Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Towards Awesome Clojure Documentation (ClojuTRE 2017)

Towards Awesome Clojure Documentation (ClojuTRE 2017)

Slide deck from my presentation at ClojuTRE 2017.

Bozhidar Batsov

September 02, 2017
Tweet

More Decks by Bozhidar Batsov

Other Decks in Programming

Transcript

  1. View Slide

  2. Божидар

    View Slide

  3. Bozhidar

    View Slide

  4. Bozho
    cool

    View Slide

  5. Bozo
    not cool

    View Slide

  6. Божo

    View Slide

  7. Bug
    cool

    View Slide

  8. Magical Sofia City
    Sofia, Bulgaria

    View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. I’m an Emacs
    fanatic

    View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. bbatsov

    View Slide

  18. Ruby & Rails
    style guides

    View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. Clojure Style Guide

    View Slide

  23. Towards AWESOME
    Clojure
    doCUmentation
    by Bozhidar Batsov

    View Slide

  24. Documentation???

    View Slide

  25. View Slide

  26. View Slide

  27. What about clean and simple
    code?

    View Slide

  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))))))

    View Slide

  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."

    View Slide

  30. Developers hate writing
    documentation, right?

    View Slide

  31. Bad developers hate writing
    documentation.

    View Slide

  32. Programs must be
    written for people to read,
    and only incidentally for
    machines to execute.
    — Hal Abelson

    View Slide

  33. Documentation is a force
    for good.

    View Slide

  34. Developers who write good
    documentation are heroes.

    View Slide

  35. View Slide

  36. Is Clojure’s documentation
    awesome?

    View Slide

  37. No.

    View Slide

  38. View Slide

  39. clojure.org

    View Slide

  40. View Slide

  41. View Slide

  42. View Slide

  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)))

    View Slide

  44. clojuredocs.org

    View Slide

  45. clojure-doc.org

    View Slide

  46. conj.io

    View Slide

  47. Grimoire

    View Slide

  48. View Slide

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

    View Slide

  50. The Problems

    View Slide

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

    View Slide

  52. It’s hard to contribute
    simple documentation fixes

    View Slide

  53. Relax the rules for
    documentation improvements

    View Slide

  54. •don’t require CA for doc improvements
    •accept doc improvements via GitHub
    •have a simplified review process for doc improvements

    View Slide

  55. clojure.org already does this!!!

    View Slide

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

    View Slide

  57. Clojure itself doesn’t

    View Slide

  58. Onward to API docs

    View Slide

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

    View Slide

  60. No.

    View Slide

  61. View Slide

  62. The docstring format is bad

    View Slide

  63. •no clear formatting rules
    •it’s not editor-friendly
    •it’s hard to make out parameters and references there

    View Slide

  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)))

    View Slide

  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}

    View Slide

  66. Comparison with Emacs Lisp

    View Slide

  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."

    View Slide

  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}

    View Slide

  69. Namespace documentation

    View Slide

  70. (ns ^{:doc "edn reading."
    :author "Rich Hickey"}
    clojure.edn
    (:refer-clojure :exclude [read read-string]))

    View Slide

  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))

    View Slide

  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))

    View Slide

  73. Some def macros don’t
    support docstrings directly

    View Slide

  74. (defonce ^:dynamic
    ^{:private true
    :doc "A ref to a sorted set of symbols representing loaded libs"}
    *loaded-libs* (ref (sorted-set)))

    View Slide

  75. This shouldn’t be hard to fix,
    right?

    View Slide

  76. No unified convention about
    documentation-related metadata
    usage

    View Slide

  77. How do you handle
    deprecations?

    View Slide

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

    View Slide

  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)))

    View Slide

  80. How do you indicate how some
    macro is supposed to be
    indented by Clojure editors?

    View Slide

  81. What’s the metadata every
    function should have?

    View Slide

  82. And what about each
    namespace?

    View Slide

  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

    View Slide

  84. Specifying indentation

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  89. (do
    (something)
    (quick))

    View Slide

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

    View Slide

  91. Epilogue

    View Slide

  92. –Mahatma Gandhi
    “You must be the change you wish to see in the world.”

    View Slide

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

    View Slide

  94. Codify the best practices

    View Slide

  95. Send documentation patches
    to Clojure and Clojure projects

    View Slide

  96. Share the best practices
    and your experience with them

    View Slide

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

    View Slide

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

    View Slide

  99. Felina

    View Slide

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

    View Slide