4  Kindly-advice

(ns kindly-advice
  (:require [scicloj.kindly-advice.v1.api :as kindly-advice]
            [scicloj.kindly.v4.kind :as kind]
            [scicloj.kindly.v4.api :as kindly]))

Kindly-advice is a small library to advise Clojure data visualization and notebook tools how to display forms and values, following the Kindly convention.

4.1 Status

Kindly-advice will stabilize soon and is currently getting feedback from tool-makers.

4.2 Goal

  • provide tools with the necessary information to support Kindly

  • have sensible defaults

  • be user-extensible

4.3 Asking for advice

(-> {:value (kind/hiccup
             [:div [:h1 "hello"]])}
    kindly-advice/advise
    kind/pprint)
{:value [:div [:h1 "hello"]],
 :meta-kind :kind/hiccup,
 :kind :kind/hiccup,
 :advice
 [[:kind/hiccup]
  [:kind/hiccup {:reason :metadata}]
  [:kind/vector {:reason :predicate}]
  [:kind/seq {:reason :predicate}]]}

The :kind field is the important one, expressing the bottom line of the inference: Kindly-advice recommends the tool handles this value as Hiccup.

The tool’s job will usually be to display the :value field based on the :kind field.

In the following exmple, we are asking for advice for given form (annotated by Kindly in this example). Kindly-advice evaluates the form and adds the resulting value to complete the context. This completion will only take place if the value is missing. It is recommended that tools will take care of evaluation themselves and pass the complete context to Kindly-advice. Doing so allows the tool to handle Exceptions better, among other things. Kindly-advice checks both the form and value for metadata. The metadata might not be present on the value.

(-> {:form ^:kind/hiccup
     [:div [:h1 "hello"]]}
    kindly-advice/advise
    kind/pprint)
{:form [:div [:h1 "hello"]],
 :value [:div [:h1 "hello"]],
 :meta-kind :kind/hiccup,
 :kind :kind/hiccup,
 :advice
 [[:kind/hiccup]
  [:kind/hiccup {:reason :metadata}]
  [:kind/vector {:reason :predicate}]
  [:kind/seq {:reason :predicate}]]}

Sometimes, there is no inferred kind, as no metadata or relevant predicates say anything useful:

(-> {:form '(+ 1 2)}
    kindly-advice/advise
    kind/pprint)
{:form (+ 1 2), :value 3, :meta-kind nil, :advice []}

In some situations, the kind inferred by predicates. Kindly-advice has a list of default predicates, which can be extended by the user. In the following eexample, it recognizes a dataset created by Tablecloth.

(require '[tablecloth.api :as tc])
(-> {:value (tc/dataset {:x (range 4)})}
    kindly-advice/advise
    kind/pprint)
{:value _unnamed [4 1]:

| :x |
|---:|
|  0 |
|  1 |
|  2 |
|  3 |
,
 :meta-kind nil,
 :kind :kind/dataset,
 :advice
 [[:kind/dataset {:reason :predicate}]
  [:kind/map {:reason :predicate}]]}

4.4 Examples

Kindly-advice is used by the following projects:

For tool makers looking to support Kindly, the kind-portal implementation is a good example to start from.

4.5 Extending

One my extend kindly-advice to perform custom kind inference.

In the following example, we add our own advisor, which recognizes vectors beginning with a :div keyword as :kind/hiccup.

(def my-advisor
  (fn [{:keys [value]}]
    (if (and (vector? value)
             (-> value first (= :div)))
      [[:kind/hiccup]])))
(kindly-advice/set-advisors!
 (cons #'my-advisor
       kindly-advice/default-advidors))
(#'kindly-advice/my-advisor
 #function[scicloj.kindly-advice.v1.advisors/meta-kind-advisor]
 #function[scicloj.kindly-advice.v1.advisors/predicate-based-advisor/fn--14618])
(-> {:form '[:div [:p "hello"]]}
    kindly-advice/advise
    kind/pprint)
{:form [:div [:p "hello"]],
 :value [:div [:p "hello"]],
 :meta-kind nil,
 :kind :kind/hiccup,
 :advice
 [[:kind/hiccup]
  [:kind/vector {:reason :predicate}]
  [:kind/seq {:reason :predicate}]]}
source: notebooks/kindly_advice.clj