malli

https://github.com/metosin/malli :malli:
valerauko 2021-03-31T05:23:24.252600Z

After seeing the #Performance section of malli's readme I'm quite interested in trying it out. One thing I was wondering is how it fares at compile-time? I'm just assuming that it achieves high runtime speed by offloading as much as possible to macros. Are there any numbers how this affects eg lein startup time?

ikitommi 2021-03-31T05:29:12.254700Z

@vale good question. There are no macros in malli (ok, one mandatory). the “schema compile time” is at runtime, you can have a separate step to create an immutable & optimized validator/transformer/parser and reuse that.

ikitommi 2021-03-31T05:30:41.256100Z

in many cases, even without reuse, it’s still faster than with alternatives, e.g. with spec / spec-tools - one-shot “gimme a validator, use it once and forget it”.

ikitommi 2021-03-31T05:32:36.257200Z

malli.core (without sci) should load under 1sec from source and 0.1sec from classes.

ikitommi 2021-03-31T05:34:57.257600Z

(= int? (m/validator [:and int? int? [:and int? [:and int?]]]))
; => true

ikitommi 2021-03-31T05:46:40.258Z

(= identity
   (m/decoder
     [:map
      [:id :int]
      [:name :string]
      [:tags [:vector :string]]]
     (mt/json-transformer)))
; => true

robert-stuttaford 2021-03-31T07:24:16.259500Z

was hoping that humanize would use language like 'at least one string? is required' here, but instead, i get "unknown error". am i doing it wrong, or is this a TODO for humanize?

(m/explain [:sequential {:min 1} string?] ())

  ;; {:schema [:sequential {:min 1} string?]
  ;;  :value  ()
  ;;  :errors (#Error{:path   []
  ;;                  :in     []
  ;;                  :schema [:sequential {:min 1} string?]
  ;;                  :value  ()
  ;;                  :type   :malli.core/limits})}

  (-> (m/explain [:sequential {:min 1} string?] ())
      me/humanize)

  ;; ["unknown error"]

ikitommi 2021-03-31T07:46:12.260400Z

@robert-stuttaford just missing an error mapping for :malli.core/limits. PR welcome to malli.error.

ikitommi 2021-03-31T07:46:48.261Z

you can also play with the mappings by passing them into me/humanize:

(-> (m/explain [:sequential {:min 1} string?] ())
    (me/humanize {:errors {::m/limits {:error/message {:en "there's no limits"}}}}))
; => ["there's no limits"]

ikitommi 2021-03-31T07:52:45.261700Z

uncomplete, but close:

(defn min-max-sequence-error-message [error options]
  (str ((me/-pred-min-max-error-fn {:pred identity}) error options) " elements"))

(-> (m/explain [:sequential {:min 1} string?] ())
    (me/humanize {:errors {::m/limits {:error/fn {:en min-max-sequence-error-message}}}}))
; => ["should be at least 1 elements"]

Helins 2021-03-31T08:05:45.267200Z

The following scheme systematically produces a stack overflow. It is a recursive definition where one item (`:E`) contains another item (which might be :E as well, or not). I am surprised this stack overflow is that systematic. There must be something wrong somewhere unless I am missing the obvious. Tried with :or instead of :multi, same thing.

(def registry
     {:A    [:tuple [:= :A]]
      :B    [:tuple [:= :B]]
      :C    [:tuple [:= :C]]
      :D    [:tuple [:= :D]]
      :E    [:tuple
             [:= :E]
             :item]
      :item [:multi
             {:dispatch first}
             [:A :A]
             [:B :B]
             [:C :C]
             [:D :D]
             [:E :E]]})


(malli.gen/generate [:schema
                     {:registry registry}
                     :item])

Helins 2021-03-31T08:07:05.267900Z

Execution error (StackOverflowError) at malli.core/-schema (core.cljc:242).

Helins 2021-03-31T08:11:19.269100Z

Validation results in the same, I probably should open an issue actually

ikitommi 2021-03-31T08:32:11.269700Z

@adam678 would [:ref :item] work? It's a lazy reference type

ikitommi 2021-04-01T08:02:05.291100Z

how about lazy generators? https://github.com/metosin/malli/pull/409

ikitommi 2021-03-31T08:33:21.270100Z

there is no recursion detection for eager references.

nilern 2021-04-01T08:35:14.295400Z

It makes sense but does leave something to be desired

ikitommi 2021-03-31T08:34:57.271Z

I Think StackOverflow is a correct way to fail here

Helins 2021-03-31T08:40:01.274300Z

In the :E definition? Such as:

[:tuple
 [:= :E]
 [:ref :item]]
I've tried it different ways but get an exception: :malli.core/invalid-ref {:ref :item} Sorry, seems I didn't drink enough coffee today 😅

Helins 2021-03-31T08:41:59.275400Z

Right, refs must be namespaced...

fugbix 2021-03-31T08:58:16.282Z

Greetings everyone!! Thank so much for this super neat lib, really cool and useful. I have a bit of an issue, whereby requiring malli.core triggers a ClassNotFoundException:

nREPL server started on port 55188 on host 127.0.0.1 - <nrepl://127.0.0.1:55188>
REPL-y 0.4.4, nREPL 0.8.3
Clojure 1.10.3
Java HotSpot(TM) 64-Bit Server VM 11.0.9+7-LTS
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=&gt; (require '[malli.core :as m])
Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:471).
malli.impl.util.SchemaError

user=&gt;
my project is leiningen and includes many dependencies (including borkdude/sci because I am using multi dispatching, Datomic, Kafka etc). I am using the latest version of malli (`metosin/malli "0.3.1"`), Of course using malli from a toy lean leiningen project works perfectly fine. Although not a Clojure expert, I’ve never seen something similar. And I really can’t imagine why malli.impl.util doesn’t load? Any help would me much appreciated, I’ll keep digging!

nilern 2021-04-01T08:40:30.295600Z

No idea, I don't see anything nonstandard going on there

fugbix 2021-04-01T12:45:15.296Z

Thank you @nilern I have removed my target directory and everything’s fine now. Of course I would love to understand what brought it in this state, but I’ll carry on with work for now. Sorry for the fuss, and thanks again for that wicked malli lib, absolutely loving it.

robert-stuttaford 2021-03-31T09:08:38.282300Z

wonderful thank you @ikitommi!

Helins 2021-03-31T09:10:21.282400Z

@ikitommi However, there is still something wrong with generation. Adding another recursive item makes things very slow (eg. 2-3 seconds for generating something like [:E [:E [:A]]]. Adding a third recursive item makes things so slow that my fans are churning and after a couple of minutes there still isn't any result.

dharrigan 2021-03-31T09:26:14.283300Z

A little while ago, there was a conversation about stripping nulls from the return back to the client

dharrigan 2021-03-31T09:26:34.283500Z

hjmmm...let me think a bit more...

ikitommi 2021-03-31T09:26:35.283600Z

please write an issue, malli should lean on https://github.com/clojure/test.check/blob/master/doc/intro.md#recursive-generators, but it does not.

dharrigan 2021-03-31T09:42:32.284200Z

So, okay, I'm doing this in the setup of the ring/router

dharrigan 2021-03-31T09:42:35.284400Z

:muuntaja (m/create
                      (assoc-in m/default-options
                                [:formats "application/json" :opts]
                                {:mapper (-&gt; (j/object-mapper)
                                             (.setSerializationInclusion JsonInclude$Include/NON_EMPTY))}))

dharrigan 2021-03-31T09:43:07.285Z

Following throught the code, I believe I'm handed back an instance of the ObjectMapper from jsonista.core when I then set that option

dharrigan 2021-03-31T09:43:28.285500Z

the thing is, if I do this (now!), then all of my coercion fails (using malli)

dharrigan 2021-03-31T09:43:44.285700Z

i.e.,

dharrigan 2021-03-31T09:43:54.286Z

"coercion": "malli",
    "errors": [
        {
            "in": [
                "repId"
            ],
            "message": "missing required key",
            "path": [
                "repId"
            ],
....
....

dharrigan 2021-03-31T09:44:17.286500Z

if I put it back to :muuntaja m/instance, then all is well with the world

dharrigan 2021-03-31T09:44:48.287Z

I'm a bit lost as to why it fails

dharrigan 2021-03-31T09:45:24.287500Z

it feels like it's not able to parse the incoming JSON data from the request

Helins 2021-03-31T09:45:34.287600Z

I did: https://github.com/metosin/malli/issues/408 I hope I am not spamming. It's just I don't quite remember having that sort of problems with Spec so I am having a bit of a hard time working with that. Not complaining though, so far using Malli has been very comfortable!

dharrigan 2021-03-31T10:37:45.288100Z

I realise this is in the wrong channel (maybe I should put into #reitit)

ikitommi 2021-03-31T13:45:41.290500Z

pushed out [metosin/malli "0.4.0"]. It’s a new MINOR as the :multi parse is changed (fixed) and leaning on the old (broken) code can break someone. https://cljdoc.org/d/metosin/malli/0.4.0/doc/changelog