George Simms
George Simms

Reputation: 4060

Documentation of records in clojure

I previously had an api which had a number of functions in it, all of which expected a map in a very particular format. When it came to documenting this API, I found that in the docstrings of each of these functions I was repeating "The map with which this function is called must be of such and such a format, and this field of the map means such and such."

So I thought it would be better for those functions to take a record, and that I could just document the record instead. However it doesn't seem to be possible to document records, at least in any way interpreted either by the doc macro or Marginalia.

A solution suggested here is "just add a :doc key in the record's meta".

I tried (defrecord ^{:doc "Here is some documentation"} MyRecord [field1 field2]) but macroexpanding this suggests it doesn't have any effect. Also defrecord returns an instance of java.lang.class which doesn't implement IMeta so I'm not sure we can give it metadata?

Upvotes: 15

Views: 2506

Answers (2)

Rachel K. Westmacott
Rachel K. Westmacott

Reputation: 2301

You can't put a docstring on the record. But if you really want to, then effectively you can.

If you want users reading the code to know your intent then you can add a comment to the code.

If you want users creating an instance of your record to have access to your docstring through tooling then you can modify the created constructor function metadata. e.g.:

(let [docstring "The string-representation *MUST* be ISO8601."
      arglists '([string-representation millis-since-epoch])
      arglists-map '([{:keys [:string-representation :millis-since-epoch]}])]
  (defrecord Timestamp [string-representation millis-since-epoch])
  (alter-meta! #'->Timestamp assoc :doc docstring)
  (alter-meta! #'->Timestamp assoc :arglists arglists)
  (alter-meta! #'map->Timestamp assoc :doc docstring)
  (alter-meta! #'map->Timestamp assoc :arglists arglists-map))

For me using a REPL in Cursive I see the argslist popup when I ask for the 'parameter info' and the docstring when I ask for 'quick documentation'.

Alternatively a better approach might be to provide your own constructor functions with standard docstrings.

Upvotes: 0

fernandohur
fernandohur

Reputation: 7154

TL;DR: Unfortunately you can't.

From the docs:

Symbols and collections support metadata

When you use defrecord you are actually creating a java class. Since classes are neither symbols nor Clojure records, you cannot append documentation to them.

More Detailed Explanation

The following REPL session shows why its not possible to append metadata to records.

user=> (defrecord A [a b])
#<Class@61f53f0e user.A>
user=> (meta A)  ;; <= A contains no metadata
nil  

The important bit to notice here is that A is a regular java class. If you try to set the metadata for A you will get an interesting error

user=> (with-meta A {:doc "Hello"}) 

ClassCastException java.lang.Class cannot be cast to clojure.lang.IObj

Apparently with-meta expects a clojure.lang.IObj. Since java.lang.Class is a a Java-land construct, it clearly knows nothing of clojure.lang.IObj.

Let's take a look now at the source code for with-meta

user=> (source with-meta)
(def
 ^{:arglists '([^clojure.lang.IObj obj m])
   :doc "Returns an object of the same type and value as obj, with
    map m as its metadata."
   :added "1.0"
   :static true}
 with-meta (fn ^:static with-meta [^clojure.lang.IObj x m]
             (. x (withMeta m))))

As you can see, this method expects x to have a withMeta object, which records clearly don't have.

Upvotes: 9

Related Questions