2

For work, I want to describe the format of a standard medical formular (used to report drugs side-effects) the most concise way. (Roughly, to render it afterwards through hiccup but not only, that's why I don't write it directly as a hiccup structure)

For instance, part of the description would be:

{"reportertitle" [:one-of "Dr" "Pr" "Mrs" "Mr"]   ; the reporter is usually the physician
 "reportergivenname" :text
 "reporterfamilyname" :text
 "reporterorganization" :text
 "reporterdepartment" :text
 ....
 "literaturereference" :text
 "studyname" :text
 ....}

The keys are standard names, I cannot change them, but I'd like to be able to easily factorize things: for instance the prefix "reporter" is highly used throughout the map, I would like to be able to factorize it, for instance by doing:

{ (prefix "reporter"
     "title" [:one-of "Dr" "Pr" "Mrs" "Mr"]
     "givenname" :text
     "familyname" :text
     "organization" :text
     "department" :text)
  .....
  "literaturereference" :text
  "studyname" :text
  ....}

But this cannot work, because I think I cannot "integrate" (splice, I believe is the correct term) the result of 'prefix', be it a function or a macro, inside the outer map.

Is there a solution to achieve this while maintaining a high level of declarativity/conciseness? (the whole form is huge and might be read by non-developers)

(As I'm new to Clojure, pretty much every design suggestion is welcome ;) )

Thanks!

Yves Parès
  • 581
  • 5
  • 14

2 Answers2

1

You are right in that a macro cannot tell eval to splice its result into the outer expression. A straightforward way around it would be to wrap the whole map definition in a macro that recognizes the prefix expressions and translates them into appropriate key-value sequences inside the resulting map definition.

You can also do it with functions only by just gluing the submaps with merge:

 (defn pref-keys [p m] (apply hash-map (apply concat (for [[k v] m] [(str p k) v])))))

 (merge
     (pref-keys "reporter"
       {"title" [...]
        "givenname" :text
         ...})
     {"literaturereference" :text
      "studyname" :text})

Which might be a bit more verbose but probably also a bit more readable.

Edit: There is one more limitation: map literals are created before any macros (inside or outside ones) are evaluated. A macro whose argument is a map literal will get a map, not some form whose evaluation would eventually produce the map. Of course the keys and values in this map are unevaluated forms, but the map itself is a proper map (IPersistentMap).

In particular this means that the literal needs to contain an even number of forms, so this:

 (my-smart-macro { (prefix "reporter" ...) } )

will fail before my-smart-macro has a chance to expand the prefix. On the other hand, this will succeed:

(another-macro { (/ 1 0) (/ 1 0) })

... provided the macro filters out the invalid arithmetic expressions from its input map.

This means that you probably do not want to pass a map literal to the macro.

Rafał Dowgird
  • 43,216
  • 11
  • 77
  • 90
  • Yeah, I guess I will have to call my macro more upstream. Can I use clojure.zip functions at compile-time to find and alter the 'prefix' forms? Or is there a more convenient way? Now that you speak about it, it seems replacing terms nested inside macro args would be quite common (I believe I saw stuff like that in Scheme). – Yves Parès Aug 09 '12 at 15:56
  • I'd propose to replace `(prefix "reporter" bla-bla-bla)` with `["reporter" bla-bla-bla]`. This way you don't need to write macro (you can write ordinary function instead). – Mikita Belahlazau Aug 09 '12 at 16:17
  • @NikitaBeloglazov: I made an edit based on your suggestion for a non-macro version. – Rafał Dowgird Aug 09 '12 at 18:19
0

In advance, I should say that this answer may not at all be what you are looking for. It would be a way of doing things that would totally alter your data structure, and you seem to maybe be saying that that's not something you can do. Anyways, I'm suggesting it because I think it would be a good change to your data structure.

So, here's how I propose you re-envision your data:

{:reporter {:title "Dr, Pr, Mrs, or Mr here"
            :given-name "text here"
            :family-name "text here"
            :organization "text here"
            :department "text here"
            ...}
 :literature-reference "text here"
 :study-name "text here"
 ...}

There are two changes I'm putting forth here: one is structural and the other is "cosmetic". The structural one is to nest another map in there for the reporter-related stuff. I personally think this makes the data clearer, and it is no less accessible. Instead of doing something like (get *data* "reportertitle") to access it, and (assoc *data* "reportertitle" *new-title*) to make a new version of it, you would instead to (get-in *data* [:reporter :title]) and (assoc-in *data* [:reporter :title]).

The cosmetic change is to turn those string-based keys into Clojure keywords. My main reasons for suggesting this are that it would be more idiomatic and that it would be potentially clearer to read your code. For a better discussion on why to use keywords see maybe here or here.

Now, I realize everything I've said pre-supposes that you actually can change how your data is structured and how the keywords are named. You said "The keys are standard names, I cannot change them", and this seems to indicate that this type of solution wouldn't work for you. However, maybe you could inter-convert between the two forms. If you are importing this data from somewhere and it already has the format that you give above, you would convert it into the nested-map-with-keywords form, and keep it that way while you did whatever you did with it. Then, when you export the data to actually be outputted or used (or whatever ultimate end it serves), you would convert it back to the form as you have it above.

I should say that I, personally, do not at all like this "inter-conversion" idea. I think it divides the notions of "code" and "data", which seems like such a shame considering it would be done only to have the code "look and feel nicer" than the data. That being said, I'm proposing it in case it sounds good to you.

Community
  • 1
  • 1
Omri Bernstein
  • 1,893
  • 1
  • 13
  • 17
  • I agree your solution would be more "clojurian". What I did so far is available here: http://goo.gl/9JK2R Note that I switched to vectors because: {} (by default?) doesn't handle ordering (and I want to be able to specify in which order the fields should appear) and "text" should be the default field format (i.e. keys don't always have values). And finally the thing is that I want to be able to specify display-related "operations" (e.g. :section indicates that a div should be created, but I don't just put "div" because as I said I don't only do HTML generation with that data). – Yves Parès Aug 11 '12 at 08:37