Reputation: 1330
Currently, as part of a coding practice exercise, I wrote the following code to read a set of space-separated pairs from stdin (as a trusted source) into a map, where nbElevators
is previously defined:
(let [elevators (into {} (doall (repeatedly nbElevators #(do [(read) (read)]))))]
Is there a better way to do this?
I don't like the #(do
, but without this, Clojure constantly interpreted the repeatedly
as an attempt to call the vector as a function, rather than treating the vector's appearence as representing a single form function which returns a vector. This seems odd because an expression is valid as a function body (eg, (defn foo [] 2)
works), yet (repeatedly 5 2)
doesn't, neither does (repeatedly 5 #(2))
- even though (repeatedly 5 (fn [] 2))
does, implying a hole in the reader macro. Am I missing something syntactic here?
Can the doall
be avoided? Is there some way to automatically cache or earmark the input so that the correct place in the file is read when sequence is evaluated lazily?
Upvotes: 0
Views: 91
Reputation: 45726
For point one, remember that in Clojure (and afaik, lisps in general), ()
s have a set, special meaning. Unless a macro rearranges things before evaluation, (f a)
means that f
will be called with a
as an argument. This isn't a "hole" in the macro. I'd argue that it would be even more surprising if #(2)
didn't attempt to call 2
. That would fly in the face of the otherwise consistent nature of how function calls work.
You can see what #()
expands to using the special form quote
:
(quote #(2))
=> (fn* [] (2)) ; fn* is the actual special form that fn delegates to
(quote #(vector %))
=> (fn* [p1__1632#] (vector p1__1632#)) ; % is translated to a generated symbol
Which shows what the problem is.
Personally, I'd use (fn [] [(read) (read)])
there for clarity. That's more verbose, but as I'll mention at the end, the code in general can be cleaned up a bit so that that's not a problem.
Of course too, the whole anonymous function could be made into an actual standalone function if that helps clarity.
And if you want the repeatedly
call to happen strictly, I'd use vec
instead of doall
and just force the lazy list into a vector. I honestly haven't found much use for doall
that vec
doesn't handle. There might be a small space-use difference, but I've never found it to be significant.
In this case though, neither is necessary. into
will force the collection returned by repeatedly
strictly. Maps aren't lazy, so it will need to be evaluated right away anyway.
And I'd just use a ->>
macro to split this so it isn't quite as long:
(let [elevators (->> (repeatedly nbElevators (fn [] [(read) (read)]))
(into {}))])
The (fn [] . . .)
bit could even the argument being threaded, but I don't think that that would read as well, even if it would lead to shorter lines.
This could all also be re-written by making use of the hash-map
"constructor" as @amolloy mentions. It takes a var-arg list and treats them as pairs. This frees you up from needing to pair the key/values yourself:
(apply hash-map (repeatedly (* 2 nbElevators) read))
Upvotes: 2