2
votes

The more I think about this problem, the more wrong it seems...

I have defined in my program something like a 'map constructor'. The idea behind this is that I have a generic map structure to handle some 'items' but I want to enforce some defaults for specific kind of items.

The problem that I have is that this 'map constructor' has a k-v pair, and that pair's value should be determined by the function that consumes this map (it might get clearer in the following example).

My first idea was to quote an expression in the value and then do an eval on it in the said function. The second idea was to replace the value with a fn, but this seems to return something similar to the quoted expression.

Let me try to depict the problem:

  • The model resulting map should be something like {:a 1 :b 2 :c 3}
  • The constructor is something like

    (defn cons-field [b]
      {:a (fn [name] (str name "!"))
       :b b
       :c "default"})
    
  • The item is created (def a-field (cons-field 5))

  • The calling function that consumes the map is something like

     (defn the-function [name field]
       (str (get-in field [:a])))
    

Now what I need is this :a's value to be a function of the parameter name in 'the-function'. Of course the last function is not working and I'm not sure if it's the correct approach anyway. The ':a' key is not always a fn; sometimes it's just a string literal. Any ideas?

Cheers!

2
In lieu of the constructor, why not just assoc to the default map? For the general problem, try to separate the logic from the data and think in terms of successive transformations on data. Can you provide more details or examples of what exactly you are trying to accomplish? - A. Webb
To merge two maps was my initial thought, but by providing a constructor, you are kind of restricting the valid inputs. I know I should be separating logic and data, but for this particular problem, I couldn't figure out a generic way to handle it. The actual problem is somewhat work related and I'm not comfortable sharing code, but the idea is the same. I need to produce some reports and for each report, this field has a different functionality. - Dimitrios K.
@DimitriosK., You can still have your constructor function that provides defaults (like for :c), just don't provide :a. Then do a transformation on the map if the value of :a needs to be determined by a function call. - Jeremy
@JeremyHeiler, yeah I created a new function outside the map and a cond on the caller to determine if it's a special case and then calls the function. :) - Dimitrios K.

2 Answers

0
votes

So this is how I solved this problem after the comments of A. Webb and Jeremy Heiler.

The initial constructor was changed to this:

(defn cons-field [b]
  {:a nil ; either delete completely or comment that
          ; the case will be handled by the caller
   :flag xx ; true or :case-qualifier
   :b b
   :c "default"})

The calling func was changed to this:

(defn the-function [name field]
  (let [case-q (:flag field)]
    (cond
      (= case-q :case-qualifier) (get-name name) ; you can have many different cases
                                              ; conciser using constants for these qualifiers
      (...) ()))) ; else as normal

Then the logic initially put in the map goes in a different func:

(defn get-name [name] (str name "!"))

Hope this helps someone else :)

0
votes

It is not really possible to understand your problem based on what you have posted. All I can do for you is tell you what your provided code does and guess what you would want it to do.

(def r (cons-field 5)) creates a hash-map r with (r :b) = 5, (r :c) = "default" and (r :a) = (fn [name] (str name "!")). As you can see, neither the result of (r :a) nor the result of (r :c) is related to r or 5.

If the results described above is what you want, it would be only logical to do some refactoring:

(def default-field {:a (fn [name] (str name "!"))
                    :c "default"})

(def cons-field (partial assoc default-field :b))

Regarding the-function: A call to (get-in field [:a]) is the same as (field :a) and will return the function (fn [name] ...). the-function will stringify this fn using str and most likely return something like "user.fn$23092...."

If you want the-function to return the result of calling (fn [name] ...) with name as passed to the function, you need to change your the-function as follows:

(defn the-function 
  [name field]
  (str ((field :a) name)))

If you want the function to return something else based on the actual value of :a, you could check if it is a function and invoke it with name, otherwise return the value.

(defn the-function
  [name field]
  (when-let [v (field :a)]
    (or (when (fn? v)
          (v name))
        v)))

Reading from your own answer now I am even more confused what actual problem you are trying to solve, but I hope that this answer could provide help.