Shortand clojure syntax for properties on hashmaps

You could try bringing things up on the ClojureScript mailing test, but just to warn you, I wouldn’t get my hopes up too much.

It is very unlikely that ClojureScript will add this unless Clojure adds it first, and it is very unlikely that Clojure will add this because Clojure is deliberately conservative in what it adds at this point.

You are of course welcome to bring it up anyway, maybe some very interesting discussion will result, but my guess is that you will most likely be told to provide these conveniences as a library instead, which is what I would also recommend.

3 Likes

I think the fact that you can provide this functionality in 3 lines of code means that it will never be added in Clojure…

2 Likes

And just because ES6 has a feature, doesn’t mean that it’s a good feature – nor that it would be a desirable feature for Clojure(Script). Languages are different for a reason. A feature in Language X might make no sense at in Language Y.

3 Likes

You’re just saying that it may not be desirable. Do you have any concrete opinion about it?

Unlike js, clojure hash-maps map any value to any value and all the clojure.core map constructors allow the creation of maps mapping any value to any value. It would be pretty inconsistent to have an nmap that requires not only symbol keys but only vars that point to a value. This seems to conflate symbols and vars, things which clojure.core tries to keep a distinction between.

I could go on but I think this introduces a ton of inconsistencies for almost no gain (it’s not really obvious where this syntactic sugar will ever even save any keystrokes!).

I don’t mean to be harsh but I think this functionality not only doesn’t belong in clojure.core, but I think this nmap is non-idiomatic clojure code and I’m having a hard time imagining where it would make sense to ever have in any clojure code.

I think it may be that this sugar is justifiable in JS due to the ways objects are commonly used there, but I think you’ll find that once you get feel for the idioms of clojure you’ll never want this.

6 Likes

Ok,

I’ll trust you guys about this one and I’ll try to advance in Clojure without this functionality.
However if I find myself writing tons of :username username lines I’ll report it back :smile:

1 Like

My concrete opinion is: that ES6 feature is terrible, as it overloads the literal syntax for two completely different things. I agree with @jjttjj and his response is exactly the sort of thing I’d expect from most seasoned Clojure devs and, esp. from the Clojure/core team, about this sort of feature.

As you say, you’re new to Clojure so right now you’re seeing a lot of differences from your “home” language without having a history of why things are different, or why your favorite feature X seems to be not only missing from Clojure but also discouraged even from being added. I’ve changed languages a lot over my 35 or so year career, and pretty much every time I pick up a new language, I’m left wondering why feature Y from the previous language is “missing”… but after a while, I get used to the fact that it uses feature Z instead (and that’s more idiomatic or easier to use), or just simply doesn’t need feature X at all.

The more languages you learn, the more you’ll see this and, in my opinion, you more you’ll come to appreciate the differences in idioms (and the differences in features) between languages.

I’m reminded of an experience, many years ago… I worked for a company that specialized in static source code analysis tools (to perform QA based on coding guidelines, bug detection through source code analysis, and software metrics calculations). The company started with a FORTRAN analysis tool, then they introduced a C analyzer (and, later, a C++ analyzer). We would go into companies, run our tooling on a large sample of their source code, and then present our findings – often uncovering bugs that their teams had been trying to track down for months. Quite a few C shops that we went into had unusual code bases (according to the metrics) and we quickly realized these were former FORTRAN shops. Once we’d spotted the pattern, it became obvious as we’d go into new C shops whether they too had migrated from FORTRAN. Their C code was particularly unidiomatic in certain ways: we dubbed it C-TRAN, because their engineers were trying to write C in the style of FORTRAN, rather than adapting to the new idioms and features.

10 Likes

Thank you for your detailed response and for sharing your experience. Your words reminded me to a history mentioned on eloquent javascript (which I’m unable to find) about someone blaming one language until he understood the differences.

Regards

2 Likes

The truth is, Clojure and ClojureScript are benevolent dictator designed languages. A feature almost never gets added unless Rich Hickey decides its a good idea.

But since Rich Hickey has pretty good intuition and insight into programming languages and Clojure’s design, it’s often for the best.

To counter act the fact that he might say no to adding a feature people want, he’s given us macros and tagged literals.

So I think its totally fine if you start creating yourself a little convenience library of things that you would have added to clojure.core, but is missing. A lot of people do that. Though I’d recommend being careful not to add too many things at first, be sure it really adds value.

Personally, I think if you have an nmap macro as above, its fine. Though I might give it a better name. That said, I’m not sure it saves a lot of keystrokes, since declaring the let binding is longer to type then just {:a "a" :b "b"}.

I can’t think of a situation where you’d call nmap more then once within that same binding, so I don’t know. But if it’s something you come accross a lot, and you’ve got good reasons for your specific use cases, then there’s nothing wrong with using that macro.

As for adding it to clojure.core, I think the truth at this point is that Clojure is already a pretty big language, with unfamiliar syntax and lots of DSLs to learn. That’s why I think new syntax is something only added if it’s absolutly necessary at this point.

One could even go a step further: suggesting design changes to a language that you do not yet fully understand is misguided. Learn how to solve a problem in idiomatic CLJS before trying to inflict upon it a solution that only makes sense in another language. After some time – and learning more about different languages and paradigms – it may turn out you didn’t need what you thought you did. Or, you might learn that you just prefer JavaScript – which is also a fine outcome!

4 Likes

things got a little strange in here. This sounds like a fantastic feature to me. I can’t tell you how many times I’ve done something like

(let [user-info (get-user-info session)
      patient-info (get-patient-info session)]
 (display-patient-info {:user-info user-info
                        :patient-info patient-info}))

I would be very for it. And especially if it made it into core so that the usage was widespread and understood. This seems to get right in line with destructuring like (let [{:keys [foo bar]}]...

1 Like

I assume something is stopping simplification to a map literal? E.g.:

(display-patient-info {:user-info (get-user-info session)
                       :patient-info (get-patient-info session)})

That’s often the direction I end up in.

3 Likes

I agree that is one way to do it. I often don’t go for this more inline style because the sites where work is done are more spread out. A lot of times I like seeing exactly what is going on in one place and then the results. The example given was a trivial example. If that turned into a map with 8 parameters I think my point would be a bit stronger.

For instance here’s an example from our codebase that is creating a handler for a react component. There’s lot of data accumulation and preparation and then the handler is created. These values are used in multiple places and further, it would be difficult to look through all of the maps to see exactly what “work” is being done.

(assoc value
       :send! #(common-actions-send handler {:graph     graph
                                             :phi       phi
                                             :uri-svc   uri-svc
                                             :ribbon    ribbon
                                             :value value
                                             :user-info user-info} %)
  :user-info user-info)
1 Like

That is the kind of repetition I wanted to avoid. This was usually a pain in the ass on javascript, and when the shorthand notation came was a bless. Short after coming to Clojure I noticed the same problem existed here, just that nobody was worried about it.
Glad to see I’m not alone here

That’s the great thing about Clojure - instead of having to wait for syntax changes to the language, you can actually identify pain points you experience and write your own macros to fix them.

Not everyone will experience this same problem, because of different coding styles, but that doesn’t matter because you have the problem, and you can something about it.

I’d advise you to start a personal/company core utility library that you use in all your projects, which contains these types of utilities that address specific problems that you experience while working on various problems. I think I got this idea from Paul Graham in On Lisp.

I think this is also a reason why it is a good thing that it is difficult to get things into core. You can, and should in my opinion, expand on core via your own libraries.

5 Likes

could select-keys satisfy your needs?

(def a {:x 1 :y 2 :z 3})
(def b (select-keys a [:x :y])) ; {:x 1 :y 2}

When I want to get a subset of keys of an object yes absolutely. When I have to build an object from scratch or apply some calculations to certain fields an build a new object not so much.

In any case, thanks for pointing me to something I didn’t know

For applying calculations to fields in a map, you can use update or update-in.

(def m
    {:foo 1
     :bar 2
     :baz 3}

(update m :bar inc)
=>
    {:foo 1
     :bar 3
     :baz 3}

For multiple fields, you can use the threading macro ->

(-> m
    (update :foo dec)
    (update :bar inc)
    (update :baz #(* % %))
=>
    {:foo 0
     :bar 3
     :baz 9}
1 Like

If you don’t like writing update several times, you could also use reduce over a sequence of key/fn pairs:

(reduce
 (fn [m [k f]] (update m k f))
 m
 [[:foo dec]
  [:bar inc]
  [:baz #(* % %)]])

… which, obvs, you could just wrap in a function to which you pass the keys/fns:

(defn update-several [m keys-and-fns]
  (reduce
   (fn [m [k f]] (update m k f))
   m
   keys-and-fns))

(update-several m
                [[:foo dec]
                 [:bar inc]
                 [:baz #(* % %)]])

The same is true for passing a bunch of paths to “modify” a nested data structure using update-in.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.