I have this set of functions - it works, but I'm concerned about the use of let
s.
(defn- create-counts [coll]
"Computes how many times did each 'next state' come from a 'previous state'.
The result type is {previous_state {next_state count}}."
(let [past coll
present (rest coll)
zipped (map vector past present)
sorted (sort zipped)
grouped (group-by first sorted)
seconds (map (fn [[key pairs]] [key (map second pairs)]) (seq grouped))
freqs (map (fn [[key secs]] [key (frequencies secs)]) seconds)]
(into {} freqs)))
I started learning functional programming in Haskell some time ago, and there, because of laziness, it's commonplace to use this equivalent of Clojure let
, because what's not needed, isn't ever computed:
asnwer = z
where
notused = map (^1000) [1000..10000]
x = [1..]
y = take 10 x
z = map (*2) y
- Is it okay to use Clojure
let
in this way? Look for example at thecreate-counts
fn definition - I use it quite heavily there. - Would it be better to just put it all inside one expression?
- What's the best practice for this?
I like how everything's named, but I don't know if there's not a performance penalty hidden somewhere.
When I try to match the Haskell example with Clojure code, it seems there's eager evaluation:
(let [notused (do (Thread/sleep (rand 10000000)) :done)
x (iterate inc 1)
y (take 10 x)
z (map #(* 2 %) y)]
z)
;=> sleeps till the judgement day
I could imagine preventing this with futures or some clever let-like macro, but really it seems to me that I'm trying to do something Haskell-y (lazy) in Clojure. What's the idiomatic way?
1 Answer 1
First of all, you could use abstract structural binding (a.k.a. destructuring) to reduce the number of let
bindings:
(defn- create-counts_org [[_ & present :as coll]]
"Computes how many times did each 'next state' come from a 'previous state'.
The result type is {previous_state {next_state count}}."
(let [zipped (map vector coll present)
sorted (sort zipped)
grouped (group-by first sorted)
seconds (map (fn [[key pairs]] [key (map second pairs)]) (seq grouped))
freqs (map (fn [[key secs]] [key (frequencies secs)]) seconds)]
(into {} freqs)))
I think there's nothing wrong in using let
bindings that are technically not necessary to improve readability and comprehensibility.
But in your function, I think it is pretty clear what every step does:
sorted
-> the first thing I see is thesort
functiongrouped
-> the first thing I see is thegroup-by
functionseconds
/freqs
-> I think these are also pretty clear, usingsecond
andfrequencies
And because each let
binding just uses the previous binding once, I would probably use the thread-last (->>)
macro:
(defn- create-counts [[_ & rest :as coll]]
"Computes how many times did each 'next state' come from a 'previous state'.
The result type is {previous_state {next_state count}}."
(->> (map vector coll rest)
(sort)
(group-by first)
(map (fn [[key pairs]] [key (map second pairs)]))
(map (fn [[key secs]] [key (frequencies secs)]))
(into {})))
I think this also answers your better to just put it all inside one expression question. Also, even if there where a performance penalty in using let
bindings, it would probably be negligible.
-
\$\begingroup\$ Thanks! I've seen the ->> macro before, but didn't think about it. Seems handy. \$\endgroup\$Martin Janiczek– Martin Janiczek2013年02月28日 09:48:45 +00:00Commented Feb 28, 2013 at 9:48
let
islet
.where
is a different beast \$\endgroup\$