stand
stand

Reputation: 3132

Can I refer to a clojure hashmap value from another value in the same map?

I'm trying to come up with some way for the values in a clojure hashmap to refer to each other. Conceptually, something like this:

(def m {:a 1 :b 5 :c (+ (:a m) (:b m))}  ;Implies (= (:c m) 6)

This doesn't work, of course since I'm circularly referencing m. I can do something like

(def m {:a 1 :b 5 :c (fn [a b] (+ a b))})
((:c m) (:a m) (:b m)) ;=> 6

but that doesn't really gain anything because I still have to know which a and b to put into the function. Another attempt:

(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
((:c m) m) ;=> 6

It's a bit better since I've now internalized the function to a map though not specifically this map. I might try to fix that with something like this

(defn new-get [k m]
  (let [v-or-fn (get m k)]
    (if (fn? v-or-fn) (v-or-fn m) v-or-fn)))

(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
(new-get :a m) ;=> 1
(new-get :b m) ;=> 5
(new-get :c m) ;=> 6

I think this is about the best I can do. Am I missing something more clever?

Upvotes: 2

Views: 715

Answers (4)

Alesya Huzik
Alesya Huzik

Reputation: 1640

Here is my take on it:

(defmacro let-map [& bindings]
  (let [symbol-keys (->> bindings (partition 2) (map first))] 
    `(let [~@bindings]
       (into {} ~(mapv (fn [k] [(keyword k) k]) symbol-keys)))))

;; if you view it as similar to let, when it's more complicated:
(let-map
  a 1
  b 5
  c (+ a b)) ; => {:a 1, :b 5, :c 6}

;; if you see it as an augmented hash-map, when it's simple enough:
(let-map a 1, b 5, c (+ a b)) ; => {:a 1, :b 5, :c 6}

Upvotes: 0

Ankur
Ankur

Reputation: 33637

Couldn't help myself from writing a macro:

(defmacro defmap [name m]
  (let [mm (into [] (map (fn [[k v]] `[~k (fn [~name] ~v)]) m))]
    `(def ~name
       (loop [result# {} mp# (seq ~mm)]
         (if (seq mp#)
           (let [[k# v#] (first mp#)]
             (recur (assoc result# k# (v# result#)) (rest mp#)))
           result#)))))

(defmap m [[:a 1]
           [:b 5]
           [:c (+ (:a m) (:b m))]])

;; m is {:a 1 :b 5 :c 6}

Upvotes: 2

om-nom-nom
om-nom-nom

Reputation: 62835

As I've already said in comment above you can use let form:

(def m 
  (let [a 1 b 5] 
    {:a a :b b :c (+ a b)}))

This should be fine if you're using values that known only inside m definition. Otherwise you would better to use function parameters as @Michiel shown.

P.S. by the way you're free to use everything inside def you're usually use in clojure. Moreover, sometimes you're free to use let in sugared form inside some other forms (although this let uses different mechanisms than usual let form):

(for [x (...) xs]
     :let [y (+ x 1)]
     ; ...

Upvotes: 1

Michiel Borkent
Michiel Borkent

Reputation: 34820

Since c is a derived value, so a function, of a and b you're probably better of by defining a function that produces this map:

 (defn my-map-fn [a b]
   {:a a :b b :c (+ a b)})

 (def my-map (my-map-fn 1 2))
 (:c my-map) ;;=> 3

Upvotes: 0

Related Questions