This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-10-16
Channels
- # aws-lambda (10)
- # beginners (52)
- # boot (42)
- # cider (2)
- # cljs-dev (8)
- # cljsjs (4)
- # cljsrn (10)
- # clojars (3)
- # clojure (48)
- # clojure-conj (4)
- # clojure-dev (19)
- # clojure-italy (7)
- # clojure-norway (4)
- # clojure-russia (44)
- # clojure-spec (70)
- # clojure-uk (34)
- # clojurescript (39)
- # cursive (35)
- # data-science (11)
- # datomic (7)
- # emacs (6)
- # fulcro (2)
- # hoplon (12)
- # jobs (1)
- # juxt (18)
- # lein-figwheel (2)
- # leiningen (4)
- # luminus (9)
- # off-topic (29)
- # om (46)
- # onyx (131)
- # other-languages (24)
- # parinfer (84)
- # pedestal (10)
- # portkey (45)
- # protorepl (1)
- # re-frame (15)
- # reagent (43)
- # ring-swagger (41)
- # schema (6)
- # shadow-cljs (293)
- # slack-help (2)
- # specter (42)
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)))
I'm brand new to clojure, but would associative destructuring in the function parameter be of use here?
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
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
(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
))
this is 1) for readability 2) for performance
((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) ...@imdaveho when returns nil if the condition is false, and if you conj to nil you get a list
it’s an eager list comprehension
(using non-tail recursion)
oh waaat...ok that explains it...just had to know that property; i guess that makes it "monoidal" <- i think
one way to rewrite it
user=> (-> (conj nil)
(conj 1)
(conj 2)
(conj 3)
(conj 4)
(conj 5))
(5 4 3 2 1)
*fixedI'm brand new to clojure, but would associative destructuring in the function parameter be of use here?
@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
@noisesmith yeah that makes it suuuuper clear...it was the whole conj nil => ()
that threw me
it's called 'nil punning': http://www.lispcast.com/nil-punning
@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
yes - reduce is optimized for many inputs and it’s relatively recent
I see. Cool, thx
also it would be faster if they used *
instead of #(* %1 %2)
@byron-woodfork I’m running benchmarks on those with 1.8 to double check what I’m claiming here
using criterium so each benchmark takes a little while
I suppose I probably could've/should've done the same too before asking lol
well, I made the claim, so I should provide proof 😄
haha fair enough
@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
it’s also the smallest and easiest to read version
Nice! Thanks!
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
yeah I would be fascinated to see an example of a benchmark of something that closer matches real world code patterns
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
I’m using queues in immutants messaging module.
I don't know how active it is, but there's an #immutant channel if no one answers you here.
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.
Now I want to consume messages from that queue, one at a time in another thread.
Restarting said thread if it errors.
How would I accomplish that in clojure?
@juanjo.lenero Why do you have to use threads? I would advise using channels instead.
(defn do-work []
(if-let [msg (receive my-queue)]
(some-func-that-may-throw msg)
(do
(Thread/sleep 1000)
(recur))))
(.start (Thread. do-work))
@rinaldi I’ll look into channels, just wondered if there was a way to do it without them.
That’s where I’m at, it works, but if that thread throws I don’t know how to restart it.
@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.
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"}
Let me know if it's clear. I myself am new to this but have been using channels a lot lately.
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.
(defn parent []
(let [response (future (whatever-task))]
(when realized? response
(if @response nil ;; Assuming you return `nil` for errors
(recur)
(println "It worked")))))
Yea, I think something like that can work, thank you
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