How to annotate that the parameter type of a function should be a map?

Hello Clojurists,
I am a beginner with Clojure and making my way through Higginbotham’s CftBT. Here is my newbie question. Suppose I have a function like this

(defn myfunc [input-map] (input-map :important-key))

how would I annotate that parameter input-map should be a map that contains a key :important-key?

If it is relevant to the answer, by “annotate” I mean some kind of code decoration so that I can automatically generate (from my code) HTML documentation that will tell the reader that the input-map parameter to myfunc is expected to be a map containing the key :important-key. For my purposes, if this annotation also helps with avoiding run-time bugs (such as a parameter of the wrong type being passed) that is a bonus. For my present purposes, I’m not worried about performance so much (avoiding reflection, etc.) as I am still just learning.

Thanks for your help.

Take a look at Clojure spec.

1 Like

Its a little verbose currently (there are some libs that help reduce the verbosity, and I think they are working on coming up with a less verbose syntax), but as borkdude said, you’d use spec as such:

(ns foo
  (:require [clojure.spec.alpha :as s])

(s/def ::important-key any?)

(s/fdef myfunc
  :args (s/cat :input-map (s/keys :req-un [::important-key]))
  :ret map?)

(defn myfunc [input-map]
  (input-map :important-key))

A less formal, and less verbose alternative way some people use, is:

  • Just document in it the doc-string like so:
(defn myfunc
  "Does something.

   `input-map` - a map with required keys `:important-key`
     `:important-key` - an id that bla bla"
  [input-map]
  (input-map :important-key))
  • And something which is between the two in term of formality which people often use as well is destructuring:
(defn myfunc
  [{:keys [important-key]}]
  (input-map important-key))

All three method will appear in the doc when you call (doc myfunc).

1 Like

You could also use preconditions.

(defn myfunc [input-map]
  {:pre [(map? input-map) (contains? input-map :my-key)]}
  (input-map :important-key))

See:
http://blog.fogus.me/2009/12/21/clojures-pre-and-post/
https://clojure.org/reference/special_forms#_fn_name_param_condition_map_expr_2

1 Like

In ten years of Clojure I’ve hardly ever seen :pre/:post conditions used in the wild. I don’t know why they never become popular. I suspect it’s because AssertionError doesn’t play nice with catching an Exception (you need to catch Throwable instead) and so :pre/:post have remained less popular than assert, which also doesn’t seem to be very widely used in the wild.

I do use assert occasionally and very, very occasionally I use :pre/:post. If I want argument validation in a function, I’m much more likely to use an explicit check and then throw an IllegalArgumentException with information about what’s wrong with the argument. I think that’s a much more friendly approach in library code.

3 Likes

I use :pre sometimes when exploring different ways to structure my data (I suspect it’s related to having to work on clojure 1.7 in prod in my last job, where spec was not really an option), and I find it practical, both as “live documentation”, and for debugging.

1 Like

You should put this information in a human readable docstring. There are many tools to produce HTML API documentation from docstrings, for example cljdoc.

1 Like