code-reviews

Chase 2020-06-17T15:49:05.012300Z

Would anyone mind taking a quick look at my small temperature converter program? The actual "business logic" is only a couple of lines but I'm more curious on how you would approach taking in the user input and validating it. That takes so much more code. It's still half the LOC of my Rust implementation of the same exercise. https://github.com/Chase-Lambert/temperature-converter/blob/master/src/temperature_converter/core.clj

2020-06-17T15:51:15.012900Z

I see multiple places where you can move forms to eliminate duplicate code

2020-06-17T15:51:26.013200Z

never use a namespace by full name - always require and provide an alias

Chase 2020-06-17T15:51:50.014Z

even if only using once huh? Ok, ok, I can dig it.

2020-06-17T15:51:52.014100Z

some clojure environments preload clojure.string, but that's not a guarantee that clojure makes, and your code can easily break if you don't require it

Chase 2020-06-17T15:52:05.014400Z

Ahh

2020-06-17T15:53:51.016200Z

dedupe example: change

(if (= x "y")
  (println "foo" (frob x) "y")
  (println "foo" (grub x) "z"))
vs.
(println "foo"
         (if (= x "y") (frob x) (grub x))
         x)

2020-06-17T15:55:17.017200Z

also at the end you can replace (if cond? (do (f) (recur)) (recur)) with (when cond? (f)) (recur) since let already has a do block

Chase 2020-06-17T15:56:22.017900Z

Ahh! You mean when is like having a do block?

2020-06-17T15:56:56.018400Z

it is, but what I mean, is that both branches of if called recur, so you can take the recur out of the if

2020-06-17T15:57:09.018800Z

and let already has a do block, so you can just put the recur after the if

2020-06-17T15:57:18.019100Z

and an if with only one branch can be replaced with when

2020-06-17T15:57:25.019400Z

(I hope that shows all my work...)

Chase 2020-06-17T15:57:38.019600Z

Ok, I get it. Thanks so much!

2020-06-17T15:58:47.020300Z

some more suggestions: in clojure 1.0 is a double, not float, and there are few reasons for using Float/parseFloat instead of Double/parseDouble

2020-06-17T15:59:00.020600Z

all clojure math functions already promote float to double as well

2020-06-17T15:59:48.021300Z

(drop-last temp) - using collection operations on strings works in simple examples like this, but it's a code smell and extremely inefficient

2020-06-17T16:01:10.022400Z

also why are you even dropping anything from what you read?

Chase 2020-06-17T16:01:37.023200Z

Well the user input comes in like "100C" so I want to split that into it's two relevant parts

2020-06-17T16:01:43.023400Z

oh! you are extracting a digit

Chase 2020-06-17T16:02:43.023700Z

How would you approach parsing that string?

2020-06-17T16:03:06.024Z

(ins)user=> (re-matches #"(\d+)(.)" "100C")
["100C" "100" "C"]

2020-06-17T16:03:33.024700Z

with a little fiddling that also helps you handle optional whitespace in a human friendly way

2020-06-17T16:03:35.024900Z

(bonus)

Chase 2020-06-17T16:03:43.025100Z

ahhh, ok. I remember doing something like that on an exercism problem I did back in the day. Regex's are still difficult for me.

2020-06-17T16:04:23.025400Z

fixed - yea res are weird

(ins)user=> (re-matches #"([\d\.]+)(.)" "100.3C")
["100.3C" "100.3" "C"]

Chase 2020-06-17T16:06:23.026700Z

so then in my let block I could use destructuring to bind to degrees and scale like [[_ degrees scale] (re-matches ...)] and continue on with my try/catch checks?

2020-06-17T16:06:38.027Z

right - that sounds correct to me

Chase 2020-06-17T16:06:51.027500Z

but what if the re-matches fails because they didn't input it right... hmmm

2020-06-17T16:07:14.028100Z

also (or (= x y) (= x z)) can be replaced by (#{y z} x) if both y and z are non nil and non-false

2020-06-17T16:07:39.028900Z

@chase-lambert in that case, re-matches returns nil, the destructure offers nil, and the behavior is the same as your current code

Chase 2020-06-17T16:07:53.029300Z

oh perfect, which they should be since they are at the end of the and statement that already checks if they are nil and false

2020-06-17T16:09:02.029800Z

oh I meant (and degrees scale (or (= scale "C") (= scale "F"))) can be (and degrees (#{"C" "F"} scale))

2020-06-17T16:09:12.030Z

but maybe that's what you meant too :D

Chase 2020-06-17T16:09:47.030500Z

yup except I still was double checking that scale was there. haha. this is great

2020-06-17T16:10:16.031Z

right - the check that scale is either "C" or "F" already checks that it wasn't nil or false, implicitly

2020-06-17T16:10:24.031300Z

I should do my refactor suggestions in smaller steps

2020-06-17T16:10:44.031700Z

@chase-lambert often a clojure refactor feels like solving a sudoku or something

2020-06-17T16:11:12.032200Z

you should notice your code becoming smaller and easier to understand at each step

Chase 2020-06-17T16:11:17.032300Z

I just watched a great sudoku solving video, you might have seen the same.

2020-06-17T16:11:29.032800Z

oh the one with the knights moves?

Chase 2020-06-17T16:11:36.033100Z

yup!

2020-06-17T16:12:01.034Z

yeah, that sort of puzzle solving and simplifying clojure code scratch the same spot in my brain

Chase 2020-06-17T16:12:41.034800Z

I like all the changes but the regex still concerns me. I can see what it does but could I recreate it myself. Could you parse your regex into an English sentence for me? "Ok, so once you hit a digit, divide this into blah blah blah"?

Chase 2020-06-17T16:13:24.035300Z

I definitely felt icky doing it my way with the drop-last and such though. I just sensed that it wasn't efficient.

2020-06-17T16:17:22.035800Z

find 1 or more of "digit or .", followed by a single token

2020-06-17T16:17:58.036300Z

[] - group, matches any item in the group \d - any digit \. - matches ".", since . is special + - one or more of preceding . - any token () - another kind of group - return string matched by this group separately

2020-06-17T16:19:52.036800Z

I think that covers it

Chase 2020-06-17T16:21:00.038100Z

Ahh ok, I'm actually writing these in a notebook by hand right now. Haha. Old school

2020-06-17T16:21:05.038300Z

one more refactor: since (System/exit 0) already causes the rest of the function body to be skipped, you can remove the let from the if, and reducing nesting makes things more readable

Chase 2020-06-17T16:21:42.038600Z

How so?

2020-06-17T16:23:59.040700Z

(if cond?
  (exit)
  (f)) 
;; vs
(when cond? (exit))
(f) 
those both dothe same thing

Chase 2020-06-17T16:27:01.042Z

ok, ok. so then my code would be

(let [temp (read-line)] 
  (when blah blah) 
  (let [[_ degrees scale blah blah 
So just taking out one level of nesting

2020-06-17T16:27:09.042200Z

right

2020-06-17T16:28:10.043100Z

the same thing I suggested with pulling the two recur calls out of the if, I think

Chase 2020-06-17T16:34:04.045Z

Good stuff. Ironically it all ends up at the same LOC but much cleaner. Did I misinterpret anything? https://github.com/Chase-Lambert/temperature-converter/blob/master/src/temperature_converter/core.clj

2020-06-17T16:35:19.045700Z

I think the "F" and "C" in the conert-temp defn should be moved out and just be scale as an arg to str

Chase 2020-06-17T16:35:52.046200Z

But it's gotta be the opposite scale

Chase 2020-06-17T16:36:02.046600Z

I'm printing "32F = 0.00C"

2020-06-17T16:36:03.046700Z

oh! I missed that

2020-06-17T16:37:06.047600Z

I'd use ({"C" "F" "F" "C"} scale) and call that inside str, instead of hard-coding

2020-06-17T16:37:24.048100Z

and using an extra arg to format like that is odd, I'm surprised it works

2020-06-17T16:38:32.048800Z

otherwise yes, that looks right - simplified significantly even if the line count hasn't changed

2020-06-17T16:39:41.049300Z

my repl shows that the format call doesn't break but the C or F is silently ignored

2020-06-17T16:40:00.049700Z

of course instead of the lookup above, the F or C could be inside the format string

Chase 2020-06-17T16:43:18.050700Z

I'm just not sure on how I want to format the expression now. Whenever I throw an if statement or whatever into the middle of a string formation I start to struggle to see what is happening. But this seems funky too:

(defn convert-temp [degrees scale]                                                
  (str degrees                                                                    
       scale                                                                      
       " = "                                                                      
       (if (= scale "C")                                                          
         (format "%.2f" (+ 32.0 (* degrees 1.8)))                                 
         (format "%.2f" (/ (- degrees 32.0) 1.8)))                                
       ({"C" "F" "F" "C"} scale))) 

Chase 2020-06-17T16:44:20.051100Z

Maybe a little less vertical:

(defn convert-temp [degrees scale]                                                
  (str degrees scale " = "                                                        
       (if (= scale "C")                                                          
         (format "%.2f" (+ 32.0 (* degrees 1.8)))                                 
         (format "%.2f" (/ (- degrees 32.0) 1.8)))                                
       ({"C" "F" "F" "C"} scale)))

Chase 2020-06-17T16:44:48.051600Z

I think my original might still be cleaner because it's such a simple either/or conversion

2020-06-17T16:45:31.052Z

yeah - that's definitely funky

2020-06-17T16:45:48.052400Z

a let block would help I think

Chase 2020-06-17T16:46:17.053300Z

ahh, yeah that could be nice. let me try that

2020-06-17T16:47:36.054600Z

(let [degrees (if (= scale "C") ...)
      degree-string (format ...)
      degree-indicator ({"C" "F" "F" "C"} scale)]
   (str degrees scale " = " degree-string degree-indicator))

2020-06-17T16:47:40.054800Z

something like that

2020-06-17T16:48:03.055200Z

also, if you held onto the original read-in line, you could use it in your output

2020-06-17T16:48:13.055500Z

instead of piecing it back together manually

Chase 2020-06-17T16:48:43.055800Z

ooh, true. Waste not

Chase 2020-06-17T16:52:33.056200Z

Alrighty, we added 2 lines but I think we can say mission accomplished. https://github.com/Chase-Lambert/temperature-converter/blob/master/src/temperature_converter/core.clj

2020-06-17T16:53:49.057Z

oh - btw. functions act as recur targets, so you can literally just remove the (loop [] ...) and replace it with the ... that was in it, without changing the behavior

2020-06-17T16:54:11.057400Z

almost forgot to mention that one

Chase 2020-06-17T16:54:12.057500Z

but won't I re-print my beginning messages that way?

2020-06-17T16:54:25.057700Z

oh right, never mind then

Chase 2020-06-17T16:54:41.058100Z

Yup. I thought of that one originally. Thanks for all your help!

Chase 2020-06-17T16:55:34.058700Z

I'm hooked on this code reviewing so you might have awakened a dragon here. Haha. I'm just solo learning so this slack is where it's at.