Fork me on GitHub

I recently added static type checking for malli schemas to Typed Clojure, here's a writeup on how to do it

馃帀 7
鉂わ笍 1
Ben Sless10:03:06

How does it interface with custom registries and custom schemas?


Cool! What is the tooling like nowadays? Does it require REPL (so it is one the same classpath as the running app) or could it be integrated into clojure-lsp?


@UK0810AQ2 it's a right now for custom schemas, a big case. Could probably be a multimethod, do you have an example of a custom schema to help me understand the design space? And custom registries, I'm not sure how far it gets me, but I use m/-deref when I find a :ref. That should be enough?


@U061V0GG2 requires a REPL since it macroexpands code.


Maybe if we create a custom macro rule for every macro in clojure.core we can think of a clojure-lsp version.

Ben Sless14:03:59

A good example of custom schemas would be for time types, such as Similarly for other iterop-y parse-y things, like valid URIs

馃憤 1

ok, I think we need something like (defmethod malli->Type :local-date [_] 'java.time.LocalTime) .


Maybe if the :class were propagated to the schema itself it would be even simpler.

Ben Sless14:03:55

So if those schemas had a way to query their backing class it would make it easier to participate in typed.clojure?


in the simplest cases, yes. Is a "simple schema" usually tied to a class?

Ben Sless14:03:58

No, but I could petition for something like "simple class schema" which could back various boring data classes

Ben Sless14:03:07

And maybe add in malli a protocol for accessing those


that sounds perfect.


Can you think of any custom registry case that might be problematic? I just call m/deref to convert the entire schema eagerly.

Ben Sless15:03:33

Recursive schemas?

Ben Sless15:03:14

Should be fine How about dependent schemas?


probably would need a custom m/type dispatch in the (future) malli->Type multimethod.


another way these simple schemas could be automated is if Typed Clojure could know the var backing the :pred. eg., pos-int? here


because the checker has the annotations for these preds


but there's a lot of info in the json-schema props too.


perhaps a :type-properties :typedclojure/type convention might be useful too.

馃憤 1

this is great 馃檪


thanks Tommi 馃檪


welcome .pretty/prettifier, thanks @meditans for the idea. Using it to create pretty checker:

(require '[malli.instrument :as mi])
(require '[ :as pretty])

(defn check
  "check that emits pretty results"
  ([] (check nil))
  ([options] ((pretty/prettifier ::check "Check Error" (fn [] (mi/check)) options))))
Dummy pretty printer for ::check:
(defmethod v/-format ::check [_ _ results printer]
  {:body (->> (for [[f error] results
                    :let [{:keys [schema errors]} error
                          explanation (-> errors first :check :malli.generator/explain-output)]]
                 (pretty/-block "When calling:" (v/-visit (-> errors first :check :smallest first (conj f)) printer) printer) :break :break
                 (pretty/-block "We get:" (v/-visit (:value explanation) printer) printer) :break :break
                 (pretty/-block "The problem is that:" (v/-visit (me/humanize explanation) printer) printer) :break :break
                 (pretty/-block "Schema:" (v/-visit schema printer) printer)])
              (interpose [:group (v/-color :title-dark (apply str (take 30 (repeat "-"))) printer) :break :break])
              (into [:group]))})
in action:

鉂わ笍 4

the actual printer is silly here, should separate the input & output schema problems, looking forward to seeing what you @meditans cooked up for this.


but, the core-lib support pretty-anything now. the helper looks like:

(defn prettifier [type title f options]
  (let [printer (assoc (or (::printer options) (assoc (-printer) :width 60)) :title title)
        actor (::actor options reporter)]
    (fn [& args] (when-let [res (apply f args)] ((actor printer) type res) res))))


I am in fact still at a prototype stage re: precise messages (and I mainly produce hiccup to pass to #portal, which I'm trying to turn in my repl for visualization). But I'll definitely try this out and upstream improvements if I end up making them for myself. I have to say, ideally for me, this prettified error message would be more usable in the form of a data structure (from which I can generate hiccup). But I can as well keep producing it myself from the error!


I think a shared function of check-explanation to some intermediate map would be good and could be used by both the malli pretty-printer and your portal visualizer.

馃檶 1
馃槏 1