Fork me on GitHub
#beginners
<
2018-05-29
>
sova00:05:46

(try 
   (loop [user-input (read-line)] ...) 
   (catch ...) 
   (finally ... ))
how can I continue grabbing user input even when there is a catastrophic read-line error? like someone gets clever and puts in an unmatched paren (

sova00:05:08

whoa did i just stumble into macro land?! sweet

sova00:05:30

haha, nevermind, just re-ran the function by name.

seancorfield00:05:03

@sova

(loop [user-input (try (read-line) (catch Throwable _ ::bad-input))]
  (if (= ::bad-input user-input)
    (println "Sorry, didn't understand!")
    (...)))

hello25400:05:21

Is there any way to selectively unquote items inside a quoted form? Like I want '(1 + ~x + y) to evaluate to (1 + "blah" + y) if x is “blah”

hello25400:05:36

I know about syntax quoting, but that fully qualifies its symbols which I do not want

seancorfield00:05:01

@hello254 Can you provide the context for that quoted expression? Is it part of a macro?

hello25400:05:19

Yeah. Trying to write a pattern-matching utility (that doesn’t depend on core.logic!)

hello25400:05:24

I want symbols to be equivalent based on their name alone

hello25400:05:47

Fully-qualified symbols makes it hard to use across namespaces

lockdown-00:05:18

@hello254 use quasiquote

seancorfield00:05:02

You mean like

`(1 + ~x + ~'y)
@lockdown-?

seancorfield00:05:53

user=> (let [x "blah"] `(1 + ~x + ~'y))
(1 clojure.core/+ "blah" clojure.core/+ y)

lockdown-00:05:25

@seancorfield yes

lockdown-00:05:44

I don't know if its called quasiquote in clojure though

lockdown-00:05:37

@yeh hmm, true, I would also though that `(1 ~x 3) would throw an error if x is not defined

lockdown-00:05:14

it creates the var if it doesn't exist

hello25400:05:52

@lockdown- In the single-quoted form, '(1 ~x 3) would place a ~x symbol inside the list

hello25400:05:10

user> '(= ~x y)
(= ~x y)

lockdown-00:05:21

well in "syntax quoting" you need to have the symbol defined somewhere anyway

lockdown-00:05:48

if not what's the purpose?

lockdown-00:05:51

or do you want it to throw an error?

hello25400:05:33

user> (let [x 10] `(~x y 3))
(10 user/y 3)
In syntax quoting, y gets fully qualified. I’m trying to avoid that if possible

hello25400:05:29

I want something more like

user> (let [x 10] '((???)x y 3))
(10 y 3)

mfikes00:05:42

@hello254

(let [x 10] `(~x ~'y 3))

hello25400:05:51

@mfikes Yup, that works. Thanks!

hello25400:05:14

(Also thanks to @lockdown-, just saw your earlier answer 😅 )

stardiviner02:05:37

What's the ^:bench in

clojure
(defn ^:bench profile-me [ms]
 (println "Crunching bits for" ms "ms")
 (Thread/sleep ms))

jeremy64203:05:58

@stardiviner It’s metadata on the function equiv to ^{:bench true}

noisesmith03:05:08

typically for something like that there's a build or dev process that loads all the namespaces and collects vars with certian metadata

noisesmith03:05:26

this is how test selectors work, for example

noisesmith03:05:53

I bet the project definition file would be informative

stardiviner03:05:59

@jeremy642 Is there difference with ^:bench before and behind function name?

jeremy64203:05:14

Hmm, putting it before the symbol applies the meta to the symbol. You can verify this with the following.

(meta #'profile-me)

jeremy64203:05:03

I’m not quite sure where it goes when you put it after.

jeremy64204:05:27

I found it applied to the arglist (as opposed to the symbol).

stardiviner04:05:24

I see. Thanks

kushalkanungo199105:05:43

Hi, I was trying to do a infinite loop but getting Can recur from tail only. What is the correct way to do this?

(def options ["1" "2" "3" "4" "5" "6"])
(loop []
      (do (println "*** Sales Menu ***")
              (println "------------------")
              (println "1. Display Customer Table")
              (println "2. Display Product Table")
              (println "3. Display Sales Table")
              (println "4. Total Sales for Customer")
              (println "5. Total Count for Product")
              (println "6. EXIT")
              (def choice (read-line))
              (if (= nil (some #(= choice %) options))
                  ((println "Please PLEASE!! Enter properly!!") (recur))
                  (do
                      (if (= choice 1 ) ((doall (for [i cust] (do (println (str (subs (str i) 2 3) ": [" (subs (str i) 5 (count (str i)))))))) (recur)))
                      (if (= choice 2 ) ((doall (for [i prod] (do (println (str (subs (str i) 2 3) ": [" (subs (str i) 5 (count (str i)))))))) (recur)))
                      (if (= choice 3 ) ((doall (for [i sales] (do (println (str (subs (str i) 2 3) ": [" (subs (str i) 5 (count (str i)))))))) (recur)))
                      (if (= choice 4 ) (
                          (do (println "Enter Customer Name: ")
                              (def cust_name (read-line))
                              (println (str cust_name ": "( cust_tot cust_name )))) (recur)))
                      (if (= choice 5 ) (
                          (do (println "Enter Product Name: ")
                              (def prod_name (read-line))
                              (println (str prod_name ": "( prod_tot prod_name )))) (recur)))
                      (if (= choice 6 ) (println "Good	Bye"))

                      ))))

xtreak2905:05:39

Some general comments : * You can use cond to replace all do if. Even better you can use case case : https://clojuredocs.org/clojure.core/case. * Repetitive println calls to print menu can be replaced with a for loop even better a print menu function. * Try using let statements instead of defs for choice, prod_name etc since they are local bindings * using - is encouraged over underscores in functions like cust_tot which reads better with customer-total

kushalkanungo199105:05:26

But I would still get tail error since I want an infinite loop and if I get wrong input I want to recur. The tail error happens at the first if

xtreak2905:05:42

Some improved code. It might not compile this is how I will refactor this :

(defn print-menu
  (let [menu  ["*** Sales Menu ***"
               "------------------"
               "1. Display Customer Table"
               "2. Display Product Table"
               "3. Display Sales Table"
               "4. Total Sales for Customer"
               "5. Total Count for Product"
               "6. EXIT"]]
    (doseq [item menu]
      (println item))))


(let [options ["1" "2" "3" "4" "5" "6"]]
  (loop []
    (do print-menu
        (let [choice (read-line)]
          (if (= nil (some #(= choice %) options))
            ((println "Please PLEASE!! Enter properly!!") (recur))
            (case choice
              1
              (do
                (doseq [i cust]
                  (do (println (str (subs (str i) 2 3) ": [" (subs (str i) 5 (count (str i)))))))
                (recur))
              2
              (do
                (doseq [i prod]
                  (do (println (str (subs (str i) 2 3) ": [" (subs (str i) 5 (count (str i)))))))
                (recur))
              3
              (do
                (doseq [i sales]
                  (do (println (str (subs (str i) 2 3) ": [" (subs (str i) 5 (count (str i)))))))
                (recur))
              4
              (do (println "Enter Customer Name: ")
                  (let [customer-name (read-line)]
                    (println (str customer-name ": " (cust_tot customer-name)))) (recur))
              5
              (do (println "Enter Product Name: ")
                  (let [product-name (read-line)]
                  (println (str product-name ": " (prod_tot product-name)))) (recur))
              6
              (println "Good    Bye")))))))

xtreak2905:05:39

By tail error do you mean recur can be used only in tail calls or do you have some kind of stack overflow due to infinite recursion ?

kushalkanungo199105:05:29

((println "Please PLEASE!! Enter properly!!") (recur))
This is where I get
java.lang.UnsupportedOperationException: Can only recur from tail position

xtreak2906:05:31

You can change them to case statements and have please enter properly as the last catch all clause. Ex :

user=> (loop []
  #_=>   (case (read-line)
  #_=>     "1"  (do (println "wrong") (recur))
  #_=>     "2"  (do (println "wrong") (recur))
  #_=>     "3"  (do (println "correct"))
  #_=>     (do (println "invalid option") (recur))))
1
wrong
2
wrong
5
invalid option
3
correct
nil
In you case it will be
(case choice
1 (do-for-1)
2 (do-for-2)
(do (println "Please PLEASE!! Enter properly!!") (recur))

seancorfield06:05:47

This is not legal @kushalkanungo1991 ((println "Please PLEASE!! Enter properly!!") (recur)) -- you are invoking println which returns nil and then you are trying to invoke nil as a function with (recur) as an argument.

seancorfield06:05:17

You need (do (println ...) (recur)) there.

kushalkanungo199106:05:12

Yep figured that part though, but then the rest of the if statement wont work as recur wont be at tail end. Now I am really confused about how to create infinite loop with break on condition.

seancorfield06:05:47

The only advice I can give is to take a step back and try to go back to basics. Clojure is an expression-based language -- you're still trying to write statement-based code.

seancorfield06:05:25

if should never have one arm, always two. That's part of why your recur isn't in the tail position. You have multiple if expressions stacked up like statements.

seancorfield06:05:54

The body of your loop should be a single expression, not a sequence of expressions. That will let you use recur.

seancorfield06:05:18

You're also still trying to use for as if it were a "for-loop" rather than a comprehension that produces a sequence. And you have additional ( .. ) wrapped around those (doall (for ...)) expressions -- again, not valid.

seancorfield06:05:34

If you just want to loop over something and print each element of a sequence, use doseq:

(doseq [i sales] (println ...))

seancorfield06:05:53

Then if you wrap that with (do ... (recur)) you're a step closer.

seancorfield06:05:06

But you also need to replace that series of separate if expressions with a single conditional cond and then you'll have a single expression.

kushalkanungo199106:05:52

(if (condition? ...)
  (then-expression ...))
Isnt this valid?

seancorfield06:05:22

My point is that you have

(if (condition)
  (then-expression))
(if (condition)
  (then-expression))
(if (condition)
  (then-expression))
...
Multiple expressions. That's why you can't use (recur) -- only the last expression can be "tail" position.

kushalkanungo199106:05:06

I can do that for cond?

seancorfield06:05:46

(cond (condition) (then-expression)
      (condition) (then-expression)
      (condition) (then-expression))
is a single expression

kushalkanungo199106:05:08

Ohhh...It doesnt fall through!!

seancorfield06:05:39

Clojure is expression-based not statement-based 🙂

seancorfield06:05:47

user=> (let [x 42]
(cond (< 50 x) "big"
      (< 0 x 50) "medium"
      (< x 0) "negative"))
"medium"
user=>
each expression is tested until the first truthy one is found, and only that one is evaluated.

seancorfield06:05:09

Note that these are expressions, not statements.

seancorfield06:05:08

I also just noticed that you're doing (read-line) which gives you a string but you're testing choice against integers -- so those will never match.

seancorfield06:05:55

(let [choice (read-line)]
  (case choice
  "1" ...
  "2" ...
  "3" ...
  "4" ...
  "5" ...
  "6" ...
  (println "Please PLEASE!! Enter properly!!"))
  (when-not (= choice "6")
    (recur)))

seancorfield06:05:07

case takes pairs of values and expressions and then a default expression. Since you are only doing stuff for side-effects for each choice, you can do the whole thing in a case expression (and ignore the nil result) and then you can have a single expression which conditionally does the recur -- and which will be in tail position.

seancorfield06:05:13

Does that help?

seancorfield06:05:58

Overall tho', you are trying to write Clojure in a very imperative style which really doesn't work. You have a def in the middle of all that -- def always defines a global variable -- you shouldn't nest def or defn inside another def or defn.

lockdown-16:05:07

(reduce * (range 1 100N)) why does it throws integer overflow?

andy.fingerhut18:05:54

Also (range 1 100N) returns a sequence of longs, not BigInt's. If you instead use (range 1N 100), it returns a sequence of BigInt's, and then the normal * function will preserve them as BigInt's.

andy.fingerhut18:05:08

I didn't have that memorized -- just tried out (range 1 100N) and then (range 1N 100) in a REPL, and saw the difference in return values.

lockdown-16:05:13

is this a jvm memory setting?

noisesmith16:05:37

it creates a number too large to fit in a long

noisesmith16:05:54

*' auto-promotes to bignums that can hold larger values

lockdown-16:05:33

ah, didn't know about *' :+1:

bertofer17:05:08

Is it idiomatic / problematic to do side effects in a for? My case is that, at the same time I need to return a value to create a list, I need to do something with each specific value (change an atom with swap!)

seancorfield17:05:54

@bertofer Could you separate the effect from the computation?

bertofer17:05:38

Like 1 for and 1 doseq? I could, yes

noisesmith17:05:42

it's problematic to use side effects in for, because it's lazy

noisesmith17:05:38

does the side effect need to happen before calculating the next value?

noisesmith17:05:50

if so, you should skip using laziness altogether and use eg. reduce or loop, if no a separate for and doseq (or run!) definitely work

bertofer17:05:46

I can do the separate doseq and for I think, thanks!

aj07mm23:05:38

I have a question: coming from a OO backgroung I didn't grasped yet how to represent a database entity in clojure. Should I use a specific data structure for that or this is a wrong question to ask in terms of how functional programming works?

akiroz23:05:17

@aj07mm If it's associative data (key-value pairs) you'd stick it in a map