Fork me on GitHub
#beginners
<
2017-10-16
>
byron-woodfork16:10:54

Anyone have any recommended naming conventions around bindings that are also arguments to a function? I'm not a huge fan of having the binding be the same name as the argument being passed in. I feel it can be a bit confusing. Maybe that's just me though. Thoughts?

(defn do-thing [arg]
  (loop [arg arg]
    ; do-stuff
    (recur arg)))

mk17:10:02

I'm brand new to clojure, but would associative destructuring in the function parameter be of use here?

dpsutton16:10:00

that's super generic. normally functions are more like (defn process-orders [orders] (loop [order orders] .... where it works on some conceptual object like orders, options, some kind of plural if you are looping. And I use the singular plucked from the pluralized collection

noisesmith17:10:17

if the arg changes on each loop but remains conceptually the same thing, it’s a state accumulator and that could be reflected in the name

noisesmith17:10:08

(also in all cases where arg is a collection being consumed starting in the beginning and in order, the loop should be replaced with a reduce, even if you need to short-circuit (see reduced))

noisesmith17:10:30

this is 1) for readability 2) for performance

imdaveho17:10:54

((fn foo [x] (when (> x 0) (conj (foo (dec x)) x))) 5)
^ from 4clojure (simple recursion) not sure how the result gets put into a list...is it an effect of the when? obviously you can't do (conj (conj 5) 4) ...

noisesmith17:10:38

@imdaveho when returns nil if the condition is false, and if you conj to nil you get a list

noisesmith17:10:48

it’s an eager list comprehension

noisesmith17:10:54

(using non-tail recursion)

imdaveho17:10:09

oh waaat...ok that explains it...just had to know that property; i guess that makes it "monoidal" <- i think

noisesmith17:10:27

one way to rewrite it

user=> (-> (conj nil)
           (conj 1)
           (conj 2)
           (conj 3)
           (conj 4)
           (conj 5))
(5 4 3 2 1)
*fixed

noisesmith17:10:54

@imdaveho also, that rewrite should make it obvious how to rewrite (perhaps as a loop or iterate) in order to reduce stack usage and improve code clarity

imdaveho17:10:42

@noisesmith yeah that makes it suuuuper clear...it was the whole conj nil => () that threw me

byron-woodfork19:10:27

@noisesmith you said loop should be replaced with reduce for performance. Is this small example study outdated then? ..It is 6 years old lol. But has reduce's performance improved since this was written? -- https://gist.github.com/tolitius/1721519

noisesmith19:10:57

yes - reduce is optimized for many inputs and it’s relatively recent

byron-woodfork19:10:18

I see. Cool, thx

noisesmith19:10:44

also it would be faster if they used * instead of #(* %1 %2)

noisesmith19:10:19

@byron-woodfork I’m running benchmarks on those with 1.8 to double check what I’m claiming here

noisesmith19:10:46

using criterium so each benchmark takes a little while

byron-woodfork19:10:50

I suppose I probably could've/should've done the same too before asking lol

noisesmith19:10:09

well, I made the claim, so I should provide proof 😄

byron-woodfork19:10:07

haha fair enough

noisesmith19:10:00

@byron-woodfork as of 1.8, slow-factorial is the fastest one on that page

user=> (defn fast-factorial [number]
  #_=>   (loop [n number factorial 1]
  #_=>     (if (zero? n)
  #_=>       factorial
  #_=>       (recur (- n 1) (* factorial n)))))
#'user/fast-factorial
user=> (defn fast-no-loop-factorial
  #_=>   ([number] (fast-no-loop-factorial number 1))
  #_=>   ([number factorial]
  #_=>    (if (zero? number)
  #_=>      factorial
  #_=>      (recur (- number 1) (* factorial number)))))
#'user/fast-no-loop-factorial
user=> (defn recursive-factorial
  #_=>   ([number] (recursive-factorial number 1))
  #_=>   ([number factorial]
  #_=>    (if (zero? number)
  #_=>      factorial
  #_=>      (recursive-factorial (- number 1) (* factorial number)))))
#'user/recursive-factorial
user=> (defn slow-factorial [number]
  #_=>    (reduce * (range 1 (inc number))))
#'user/slow-factorial
user=> (crit/bench (fast-factorial 20))
WARNING: Final GC required 7.723499618768098 % of runtime
Evaluation count : 104901060 in 60 samples of 1748351 calls.
             Execution time mean : 594.096212 ns
    Execution time std-deviation : 20.328473 ns
   Execution time lower quantile : 559.761955 ns ( 2.5%)
   Execution time upper quantile : 630.269040 ns (97.5%)
                   Overhead used : 2.325356 ns
nil
user=> (crit/bench (fast-no-loop-factorial 20))
Evaluation count : 81981660 in 60 samples of 1366361 calls.
             Execution time mean : 734.338550 ns
    Execution time std-deviation : 26.108761 ns
   Execution time lower quantile : 699.180100 ns ( 2.5%)
   Execution time upper quantile : 782.081357 ns (97.5%)
                   Overhead used : 2.325356 ns
nil
user=> (crit/bench (recursive-factorial 20))
Evaluation count : 76431000 in 60 samples of 1273850 calls.
             Execution time mean : 825.640696 ns
    Execution time std-deviation : 27.265113 ns
   Execution time lower quantile : 773.731849 ns ( 2.5%)
   Execution time upper quantile : 867.129354 ns (97.5%)
                   Overhead used : 2.325356 ns
nil
user=> (crit/bench (slow-factorial 20))
Evaluation count : 138950460 in 60 samples of 2315841 calls.
             Execution time mean : 436.251334 ns
    Execution time std-deviation : 8.966446 ns
   Execution time lower quantile : 425.687944 ns ( 2.5%)
   Execution time upper quantile : 456.996317 ns (97.5%)
                   Overhead used : 2.325356 ns

Found 2 outliers in 60 samples (3.3333 %)
        low-severe       2 (3.3333 %)
 Variance from outliers : 9.3835 % Variance is slightly inflated by outliers
nil

noisesmith19:10:45

it’s also the smallest and easiest to read version

ghadi19:10:41

factorial is challenging because it's a common bench that languages use to measure each other, but it's not emblematic of real world code

noisesmith20:10:14

yeah I would be fascinated to see an example of a benchmark of something that closer matches real world code patterns

ghadi20:10:11

it's like for concurrency frameworks the standard thing to benchmark is a ring of processes passing a message like the telephone game... it's somewhat useful, but not emblematic of real code in the wild

juanjo.lenero22:10:56

I’m using queues in immutants messaging module.

seancorfield23:10:21

I don't know how active it is, but there's an #immutant channel if no one answers you here.

juanjo.lenero23:10:19

Thanks, but I have no problems with the module, I can receive messages just fine. I just can’t find a way to “run a job on a background thread under supervision and restart it if it errors, forever” in clojure.

juanjo.lenero22:10:13

Now I want to consume messages from that queue, one at a time in another thread.

juanjo.lenero22:10:30

Restarting said thread if it errors.

juanjo.lenero22:10:43

How would I accomplish that in clojure?

rinaldi23:10:40

@juanjo.lenero Why do you have to use threads? I would advise using channels instead.

rcustodio08:10:44

Is that channel from core.async?

juanjo.lenero23:10:13

(defn do-work []
  (if-let [msg (receive my-queue)]
    (some-func-that-may-throw msg)
    (do
      (Thread/sleep 1000)
      (recur))))

(.start (Thread. do-work))

juanjo.lenero23:10:55

@rinaldi I’ll look into channels, just wondered if there was a way to do it without them.

juanjo.lenero23:10:00

That’s where I’m at, it works, but if that thread throws I don’t know how to restart it.

rinaldi23:10:13

@juanjo.lenero The reason why I advise channels instead is that even though the thread API is simple in Clojure, it can be quite expensive to spawn up new ones, not to mention orchestrating things with them. Your use case sounds perfect for channels. They're pretty much lightweight threads, so you can spawn up many of them without paying too much.

rinaldi23:10:02

Imagine a channel with incoming messages where you send things to execute concurrently then another one with the responses, that you pass to each task you execute. You can keep sending stuff to the responses channel and create a loop to analyze whatever comes in there. If you got an error back it's fine, just send it back to the incoming channel. Since channels accept whatever data structure, you can use good old hash maps. Think like:

{:success false
:attempt 2
:message "Something bad"}

rinaldi23:10:53

Let me know if it's clear. I myself am new to this but have been using channels a lot lately.

juanjo.lenero23:10:59

I’m actually restricted by a third-party API that only allows 1 request/sec so I don’t need much concurrency, I just don’t want to block the main thread.

rinaldi23:10:59

Gotcha, this changes things a bit

rinaldi23:10:03

You can then simply use future and deref no?

rinaldi23:10:52

(defn parent []
  (let [response (future (whatever-task))]
    (when realized? response
      (if @response nil ;; Assuming you return `nil` for errors
                    (recur)
                    (println "It worked")))))

juanjo.lenero23:10:37

Yea, I think something like that can work, thank you

noisesmith23:10:31

in regards to the more general question of "how to do something repeatedly and retry if it fails, the general template looks like

(while (should-continue)
  (try (do-something)
     (catch Exception _)))
- as long as the error handling block is inside the looping construct it won't interrupt the processing of more input