Towards Awesome Clojure Documentation (ClojuTRE 2017)

Towards Awesome Clojure Documentation (ClojuTRE 2017)

Slide deck from my presentation at ClojuTRE 2017.

1be785d1d788b82929e55fc83a9f0aaa?s=128

Bozhidar Batsov

September 02, 2017
Tweet

Transcript

  1. None
  2. Божидар

  3. Bozhidar

  4. Bozho cool

  5. Bozo not cool

  6. Божo

  7. Bug cool

  8. Magical Sofia City Sofia, Bulgaria

  9. None
  10. None
  11. None
  12. None
  13. I’m an Emacs fanatic

  14. None
  15. None
  16. None
  17. bbatsov

  18. Ruby & Rails style guides

  19. None
  20. None
  21. None
  22. Clojure Style Guide

  23. Towards AWESOME Clojure doCUmentation by Bozhidar Batsov

  24. Documentation???

  25. None
  26. None
  27. What about clean and simple code?

  28. (defn map "Returns a lazy sequence consisting of the result

    of applying f to the set of first items of each coll, followed by applying f to the set of second items in each coll, until any one of the colls is exhausted. Any remaining items in other colls are ignored. Function f should accept number-of-colls arguments. Returns a transducer when no collection is provided." {:added "1.0" :static true} ([f] (fn [rf] (fn ([] (rf)) ([result] (rf result)) ([result input] (rf result (f input))) ([result input & inputs] (rf result (apply f input inputs)))))) ([f coll] (lazy-seq (when-let [s (seq coll)] (if (chunked-seq? s) (let [c (chunk-first s) size (int (count c)) b (chunk-buffer size)] (dotimes [i size] (chunk-append b (f (.nth c i)))) (chunk-cons (chunk b) (map f (chunk-rest s)))) (cons (f (first s)) (map f (rest s))))))) ([f c1 c2] (lazy-seq (let [s1 (seq c1) s2 (seq c2)] (when (and s1 s2) (cons (f (first s1) (first s2)) (map f (rest s1) (rest s2))))))) ([f c1 c2 c3] (lazy-seq (let [s1 (seq c1) s2 (seq c2) s3 (seq c3)] (when (and s1 s2 s3) (cons (f (first s1) (first s2) (first s3)) (map f (rest s1) (rest s2) (rest s3))))))) ([f c1 c2 c3 & colls] (let [step (fn step [cs] (lazy-seq (let [ss (map seq cs)] (when (every? identity ss) (cons (map first ss) (step (map rest ss)))))))] (map #(apply f %) (step (conj colls c3 c2 c1))))))
  29. "Returns a lazy sequence consisting of the result of applying

    f to the set of first items of each coll, followed by applying f to the set of second items in each coll, until any one of the colls is exhausted. Any remaining items in other colls are ignored. Function f should accept number-of-colls arguments. Returns a transducer when no collection is provided."
  30. Developers hate writing documentation, right?

  31. Bad developers hate writing documentation.

  32. Programs must be written for people to read, and only

    incidentally for machines to execute. — Hal Abelson
  33. Documentation is a force for good.

  34. Developers who write good documentation are heroes.

  35. None
  36. Is Clojure’s documentation awesome?

  37. No.

  38. None
  39. clojure.org

  40. None
  41. None
  42. None
  43. (defmacro when "Evaluates test. If logical true, evaluates body in

    an implicit do." {:added "1.0"} [test & body] (list 'if test (cons 'do body)))
  44. clojuredocs.org

  45. clojure-doc.org

  46. conj.io

  47. Grimoire

  48. None
  49. Something’s definitely wrong with Clojure’s documentation…

  50. The Problems

  51. Clojure’s core team doesn’t care much about documentation

  52. It’s hard to contribute simple documentation fixes

  53. Relax the rules for documentation improvements

  54. •don’t require CA for doc improvements •accept doc improvements via

    GitHub •have a simplified review process for doc improvements
  55. clojure.org already does this!!!

  56. (but I guess many people don’t know that)

  57. Clojure itself doesn’t

  58. Onward to API docs

  59. Does Clojure provide us the infrastructure to create awesome API

    documentation?
  60. No.

  61. None
  62. The docstring format is bad

  63. •no clear formatting rules •it’s not editor-friendly •it’s hard to

    make out parameters and references there
  64. (defn str "With no args, returns the empty string. With

    one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args." {:tag String :added "1.0" :static true} (^String [] "") (^String [^Object x] (if (nil? x) "" (. x (toString)))) (^String [x & ys] ((fn [^StringBuilder sb more] (if more (recur (. sb (append (str (first more)))) (next more)) (str sb))) (new StringBuilder (str x)) ys)))
  65. (defn str "With no args, returns the empty string. With

    one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args." {:tag String :added "1.0" :static true}
  66. Comparison with Emacs Lisp

  67. (defun cider-interactive-eval (form &optional callback bounds additional-params) "Evaluate FORM and

    dispatch the response to CALLBACK. If the code to be evaluated comes from a buffer, it is preferred to use a nil FORM, and specify the code via the BOUNDS argument instead. This function is the main entry point in CIDER's interactive evaluation API. Most other interactive eval functions should rely on this function. If CALLBACK is nil use `cider-interactive-eval-handler'. BOUNDS, if non-nil, is a list of two numbers marking the start and end positions of FORM in its buffer. ADDITIONAL-PARAMS is a plist to be appended to the request message. If `cider-interactive-eval-override' is a function, call it with the same arguments and only proceed with evaluation if it returns nil."
  68. (defn str "Converts something to a string. With no args,

    returns the empty string. With one arg X, returns `x.toString()`. `(str nil)` returns the empty string. With more than one arg, returns the concatenation of the string values of the args." {:tag String :added "1.0" :static true}
  69. Namespace documentation

  70. (ns ^{:doc "edn reading." :author "Rich Hickey"} clojure.edn (:refer-clojure :exclude

    [read read-string]))
  71. (ns ^{:doc "Clojure String utilities It is poor form to

    (:use clojure.string). Instead, use require with :as to specify a prefix, e.g. (ns your.namespace.here (:require [clojure.string :as str])) Design notes for clojure.string: 1. Strings are objects (as opposed to sequences). As such, the string being manipulated is the first argument to a function; passing nil will result in a NullPointerException unless documented otherwise. If you want sequence-y behavior instead, use a sequence. 2. Functions are generally not lazy, and call straight to host methods where those are available and efficient. 3. Functions take advantage of String implementation details to write high-performing loop/recurs instead of using higher-order functions. (This is not idiomatic in general-purpose application code.) 4. When a function is documented to accept a string argument, it will take any implementation of the correct *interface* on the host platform. In Java, this is CharSequence, which is more general than String. In ordinary usage you will almost always pass concrete strings. If you are doing something unusual, e.g. passing a mutable implementation of CharSequence, then thread-safety is your responsibility." :author "Stuart Sierra, Stuart Halloway, David Liebke"} clojure.string (:refer-clojure :exclude (replace reverse)) (:import (java.util.regex Pattern Matcher) clojure.lang.LazilyPersistentVector))
  72. (ns ^{:author "Stuart Sierra, Stuart Halloway, David Liebke"} "Clojure String

    utilities It is poor form to (:use clojure.string). Instead, use require with :as to specify a prefix, e.g. (ns your.namespace.here (:require [clojure.string :as str])) Design notes for clojure.string: 1. Strings are objects (as opposed to sequences). As such, the string being manipulated is the first argument to a function; passing nil will result in a NullPointerException unless documented otherwise. If you want sequence-y behavior instead, use a sequence. 2. Functions are generally not lazy, and call straight to host methods where those are available and efficient. 3. Functions take advantage of String implementation details to write high-performing loop/recurs instead of using higher-order functions. (This is not idiomatic in general-purpose application code.) 4. When a function is documented to accept a string argument, it will take any implementation of the correct *interface* on the host platform. In Java, this is CharSequence, which is more general than String. In ordinary usage you will almost always pass concrete strings. If you are doing something unusual, e.g. passing a mutable implementation of CharSequence, then thread-safety is your responsibility." clojure.string (:refer-clojure :exclude (replace reverse)) (:import (java.util.regex Pattern Matcher) clojure.lang.LazilyPersistentVector))
  73. Some def macros don’t support docstrings directly

  74. (defonce ^:dynamic ^{:private true :doc "A ref to a sorted

    set of symbols representing loaded libs"} *loaded-libs* (ref (sorted-set)))
  75. This shouldn’t be hard to fix, right?

  76. No unified convention about documentation-related metadata usage

  77. How do you handle deprecations?

  78. How do you indicate when something was introduced/ changed?

  79. (defn agent-errors "DEPRECATED: Use 'agent-error' instead. Returns a sequence of

    the exceptions thrown during asynchronous actions of the agent." {:added "1.0" :deprecated "1.2"} [a] (when-let [e (agent-error a)] (list e))) (defn clear-agent-errors "DEPRECATED: Use 'restart-agent' instead. Clears any exceptions thrown during asynchronous actions of the agent, allowing subsequent actions to occur." {:added "1.0" :deprecated "1.2"} [^clojure.lang.Agent a] (restart-agent a (.deref a)))
  80. How do you indicate how some macro is supposed to

    be indented by Clojure editors?
  81. What’s the metadata every function should have?

  82. And what about each namespace?

  83. Common Metadata • version added (e.g. “:added”) • version compatibility

    broken (e.g. “:changed”) • version when deprecated (e.g. “:deprecated”) • superseded by • related symbols (e.g. “:see also”) • indentation specification
  84. Specifying indentation

  85. (defmacro with-in-str "[DOCSTRING]" {:style/indent 1} [s & body] (bla bla

    bla))
  86. (with-in-str some-str (something) (quick))

  87. (with-in-str some-str (something) (quick))

  88. (defmacro do "[DOCSTRING]" {:style/indent 0} [& body] (bla bla bla))

  89. (do (something) (quick))

  90. http://cider.readthedocs.io/ en/latest/indent_spec/

  91. Epilogue

  92. –Mahatma Gandhi “You must be the change you wish to

    see in the world.”
  93. –Bozhidar Batsov “You must be the change you wish to

    see in the code.”
  94. Codify the best practices

  95. Send documentation patches to Clojure and Clojure projects

  96. Share the best practices and your experience with them

  97. Let’s make Clojure’s documentation better together!

  98. Let’s make Clojure’s documentation awesome together!

  99. Felina

  100. Epilogue twitter: @bbatsov github: @bbatsov http//batsov.com http://emacsredux.com ClojuTRE Tampere, Finland

    02.09.2017