Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Docstring for custom definitions

In the project I'm working on we often define custom defsomething-style macros for different purposes to hide boilerplate. One example is defhook which helps to define a hook handler for an event. Here's a simplified version of it (the actual version has more parameters and does some non-trivial things in defmethod, but that's irrelevant to my question):

(defmulti handle-hook
  "This multimethod is called when an event was fired."
  (fn [event context] event))

(defmacro defhook
  "Define a hook for an event."
  [event docstring & more]
  `(let [body# (fn ~@more)]
     (defmethod handle-hook ~event [event# context#]
       (body# context#))))

(defhook "EntryDeleted"
  "Hook called on entry deletion."
  [context]
  (log-deletion (:EntryID context)))

The main problem I have with this code is that defmethod does not support docstring, so I can't use the one for "EntryDeleted" in REPL or for automatic documentation generation. The last one is important for the project: there are defhooks and defhandlers that are exposed as external API and currently we have to maintain documentation separately (and manually).

So the simplest question is "how to attach docstring to a defmethod"?.

And the deeper one would be "how to attach/generate documentation for custom defsomething macros?"

If some of the existing tools for documentation generation supported this feature it would be great! Yet, neither of Marginalia, Codox or Autodoc seem to support something like that.

like image 719
fizruk Avatar asked Nov 10 '22 08:11

fizruk


1 Answers

How to attach/generate documentation for custom defsomething macros?

Since docstrings are attached to vars, you'd generally have defsomething macros expand into a more primitive def form, e.g. defn, def. Then you just arrange for your defsomething's docstring to be attached to the underlying var.

How to attach docstring to a defmethod?

This is a special case - defmethod is not defining a new var; it's calling a Java method on a Java object. On the other hand, defmulti does create a var. One idea would be to extend the multi-function's docstring with the dispatch value and associated description. For example,

(defn append-hook-doc! [event docstring]
  (let [hook-doc (str event " - " docstring)]
    (alter-meta! #'handle-hook
                 (fn [m]
                   (update-in m [:doc] #(str % "\n\t" hook-doc))))))
...
(doc handle-hook)
-------------------------
user/handle-hook
  This multimethod is called when an event was fired.
      EntryDeleted - Hook called on entry deletion.

As the ! indicates, this form has a side-effect: multiple evaluations of a defining form that calls this will result in duplicate lines in #'handle-hook's docstring. You might avoid this by stashing some extra metadata in #'handle-hook to use as a marker for whether or not the doc has already been appended. Alternatively, you might stash the docstrings elsewhere and patch it all together in some auxiliary step, e.g. by delaying the expansion of (defmulti handle-hook ... until you have all the docstrings (although, this breaks the open extension of multi-methods wrt docstrings).

like image 81
Ian Tegebo Avatar answered Nov 15 '22 11:11

Ian Tegebo