Fork me on GitHub
#malli
<
2022-03-25
>
ambrosebs01:03:54

I recently added static type checking for malli schemas to Typed Clojure, here's a writeup on how to do it https://github.com/typedclojure/typedclojure/tree/main/example-projects/malli-type-providers

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

How does it interface with custom registries and custom schemas?

juhoteperi11:03:31

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?

ambrosebs14:03:48

@UK0810AQ2 it's a https://github.com/typedclojure/typedclojure/blob/4818f27a4661262b0d8268a6b00bd0363b02dba0/typed/malli/src/typed/malli/parse_type.clj#L196 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?

ambrosebs14:03:42

@U061V0GG2 requires a REPL since it macroexpands code.

ambrosebs14:03:23

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 https://github.com/metosin/malli/pull/545 Similarly for other iterop-y parse-y things, like valid URIs

馃憤 1
ambrosebs14:03:18

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

ambrosebs14:03:27

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?

ambrosebs14:03:54

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

ambrosebs14:03:40

that sounds perfect.

ambrosebs15:03:32

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?

ambrosebs15:03:42

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

ambrosebs15:03:41

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

ambrosebs15:03:02

because the checker has the annotations for these preds

ambrosebs15:03:48

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

ambrosebs15:03:08

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

馃憤 1
ikitommi17:03:33

this is great 馃檪

ambrosebs21:03:46

thanks Tommi 馃檪

ikitommi17:03:45

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

(require '[malli.instrument :as mi])
(require '[malli.dev.pretty :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)]]
                [:group
                 (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:
(check) 

鉂わ笍 4
ikitommi17:03:20

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

ikitommi17:03:08

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

Carlo19:03:38

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!

ikitommi20:03:56

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