6

I have a map like this:

(def a {:a 1, :b 2})

: I wish to overload the map so that some keywords execute functions so that :

(c: a)

Can execute a function. Is this possible?

Update:

I do realise that I could do something like:

(def a {:a (fn[] 1) :b (fn[] 2) :c (fn[] x)})

:and:

((c: a))

: but then I have to convert every existing map entry I have to a function.

I want the function "reevaluated" every time. For example when I do :

(def ab{:a 1         :b 2         :c ( #(.nextInt (java.util.Random.) 1000))}) 

(str (:c ab) " " (:c ab) " " (:c ab))

I get:

61 61 61

Instead of three different numbers

Update 2

I thought about the answer I was given and realised that he is right, I should use immutable structures only. The final solution I came up with was to have an "enrich" function which creates the dynamic properties on demand.

 (def a {:a 1, :b 2})

: I wish to overload the map so that some keywords execute functions so that :

(str (:c (enrich ab)) " " (:c (enrich ab)) " " (:c (enrich ab)))

will produce different numbers each time like so:

58 639 710
yazz.com
  • 57,320
  • 66
  • 234
  • 385

3 Answers3

4

I believe it is possible to override the behaviour of associative lookups if you make your data structure a record rather than a regular map.

You basically need to override clojure.lang.ILookup : see this question for more details

Here's a quick example:

(deftype TestLookup []
  clojure.lang.ILookup
    (valAt [this k not-found]
      (str "Generated value for key - " k))

    (valAt [this k]
      (.valAt this k nil)))

(def lookupable-object (TestLookup.))

(:somekey lookupable-object)
=> "Generated value for key - :somekey"
Community
  • 1
  • 1
mikera
  • 105,238
  • 25
  • 256
  • 415
  • I did try this but it does not allow me to assoc items into it. I selected your answer even though I probably can't use it as I am so impressed that you actually tried to do this :) – yazz.com Apr 12 '11 at 10:01
2

Using the same map to sometimes pass back immutable values and sometimes pass back impure functions seems to me to be against the spirit of what immutable maps should be used for. Instead of doing this, I would recommend using a reference type which points to an immutable map with only data values. Then, when one of these data values needs to be something different, point your reference type to a new immutable map reflecting any changes.

dbyrne
  • 59,111
  • 13
  • 86
  • 103
  • I want the function to be evaluated when the field is accessed, as the value returned is computed and can change. I know this is not immutable as my simple example shows, but I just showed a simple case – yazz.com Apr 07 '11 at 16:39
  • 1
    Oh ok, so the function you want to evaluate isn't pure. – dbyrne Apr 07 '11 at 16:45
  • Yes, sorry, I didn't mention that in my original question – yazz.com Apr 07 '11 at 16:48
2

Clojure prefers pure data structures over objects that combine data and behavior. You can get the behavior you want by accessing your data structure through a function:

(def base-foo {:a 1, :b 2})

(defn foo [key]
  (if (= :c key) 
    (rand-int 100)
    (get base-foo key)))

(str (foo :c) " " (foo :c) " " (foo :c))

;;=> "66 52 25"
Stuart Sierra
  • 10,837
  • 2
  • 29
  • 35