Mark Green
Mark Green

Reputation: 1330

Is there a more elegant way to input a map as a list of pairs from stdin in Clojure?

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?

Upvotes: 0

Views: 91

Answers (1)

Carcigenicate
Carcigenicate

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

Related Questions