Fork me on GitHub
#malli
<
2023-01-26
>
escherize03:01:30

should mx/defn do type hinting automatically?

escherize04:01:01

mx/defn doesn’t grab metadata off the var, just from the metadata position, unless Im missing something. It’d be nice to pass metadata into it from the name, IMO.

ikitommi06:01:04

> mx/defn doesn’t grab metadata off the var, just from the metadata position, unless Im missing something. It’d be nice to pass metadata into it from the name, IMO. I noticed that too. But, that’s how clojure works right? but I’m onboard of making mx/defn better. auto-type-hinting would require mapping from schemas to types, right?

escherize06:01:30

I have a bunch of mx/defn improvements.

escherize06:01:30

One I just thought of is, using catn instead of cat so [:=> [:cat :int :int] :int] could become [:=> [:catn [:a int] [:c int]] :int]

escherize06:01:44

the ones I have right now, are :no-throw, :gen, :seed, and :size

escherize06:01:40

I am calling mg/generate — but I think there’s a way to use -strument to do the generation

escherize06:01:45

idk which is better

escherize06:01:32

from the function schema doc:

(def pow-gen
  (m/-instrument
    {:schema [:function
              [:=> [:cat :int] [:int {:max 6}]]
              [:=> [:cat :int :int] [:int {:max 6}]]]
     :gen mg/generate}))

👍 2
ikitommi06:01:45

I guess with symbols, so:`[:=> [:catn ['a int] ['c int]] :int]`

escherize06:01:58

symbols!! 😍

ikitommi06:01:15

yeah, the name can be anything

🧠 2
escherize06:01:55

Yeah. Does that mapping exist already? It’s mostly trivial. One question is should we it let one add a custom typehint?

escherize06:01:10

no need to allow that unless people want it.

ikitommi06:01:56

no such mappings yet. I have the schema-type -> type mapping in top of the backlog, e.g. int? is a :int. simplifies things like JSON Schema mappings would do it with (java/js) type hints too.

escherize07:01:45

That is something that will be really good to have

Karel Miarka10:01:12

Hello, please how to add a custom generator? Cannot figure out: [:fn {:generator-fn #?(:clj #(Date.) :cljs #(js/Date.))} (fn [x] (instance? #?(:clj Date :cljs js/Date) x))]

ikitommi11:01:03

search for :gen/gen from the README (requires a generator, not sure if plain function is valid here.

ikitommi11:01:40

should there be :gen/fn :thinking_face:

dharrigan11:01:58

@U04M0JEU88Z here's a little example of using a custom generator:

Karel Miarka12:01:10

Thank you both for you replies. I have fortunately found clojure.core.inst? fn to be a better replacement for my case. Still not sure how to fix my previous trouble, but the answer can be probably found by studing clojure.test.check.generators and than using :gen/gen .

Noah Bogart14:01:04

style/usage/architecture question: we're using Malli as internal schemas (instrumentation/documentation) and then also relying on the json schema transformation for input/output validation (our system has an older custom json schema builder, lol). Combined, this means our schemas are a little unwieldy and have a lot of repetition. For example, for a single POST foo endpoint, we have • NewFoo schema • post-foo-payload which is the json schema transformation of the NewFoo schema • private post-foo-decoder which is standalone to get speed from m/decoderdecode-foo-params that uses post-foo-decoder to decode the payload • PostFooResponse schema (which is thankfully just (mu/assoc NewFood :extra-key :uuid)) • private post-foo-response-encoder which is standalone to get speed from m/encoderencode-post-foo-response that uses post-foo-response-encoder to do the final-step encoding/extra key stripping before calling (compojure/response resp) • and finally post-foo-response which is the json schema transformation of the PostFooResponse schema That's a lot and some of it comes from using Malli to generate json schema schemas, I know, but there's still a fair amount of boilerplate happening. Does anyone have ideas for how to lessen the code footprint for situations like this?

ikitommi15:01:10

if that is all for a single POST, It’s a lot. There are two good ways to organize schemas: 1. Vars a. like plumatic, (def NewFoo (m/schema …)) b. simple, usually non-qualified keys 2. Global Registry a. like spec, description here: https://github.com/metosin/malli#mutable-registry b. mutable = evil, qualified keys. for serving requests over web, the boilerplate of dealing with optimized encoders & decoders can be pushed into frameworks, e.g. reitit + malli here: https://github.com/metosin/reitit/blob/master/examples/ring-malli-swagger/src/example/server.clj#L57-L81. btw, nowadays malli caches automatically validators, explainers and generators but NOT transformers. so this is almost as fast as manually creating a validator:

(def Foo (m/schema [:map [:x :int]]))

;; caches the validator on first invocation into schema instance
(m/validate Foo {:x 1})

Noah Bogart15:01:10

> nowadays malli caches automatically validators, explainers and generators Really? That's great news. > can be pushed into frameworks that's what our json schema is meant to handle, but switching over entirely to malli is long and hard and there's no desire to move off of compojure, so right now i'm stuck carving out a small corner of sanity in the much larger code base.

pithyless15:01:00

> • private post-foo-decoder which is standalone to get speed from m/decoder > • decode-foo-params that uses post-foo-decoder to decode the payload I've usually created closed over functions that internally create the decoder.

(def decode-foo-params [..] (let [decoder (..)] (fn [..] (decoder ...))

pithyless15:01:56

I've also created orchestration functions that deal with the repetitions of an operation (and you can more declaratively just pass in a map of {:my-schema ,, :post-decoder-fn ,,, :etc})

pithyless15:01:04

Arne also had a talk about data-driven malli which I think can serve as inspiration on how to generate more of this stuff based on some initial schemas. https://www.youtube.com/watch?v=ww9yR_rbgQs

Noah Bogart15:01:12

very cool, thanks for the talk. watching it now

Ben Sless16:01:17

If you care about performance then you should consider dropping compojure, otherwise you can borrow the coercion middlewares from reitit

Noah Bogart16:01:45

hah I would love to drop compojure. getting organizational buy-in is quite challenging, sadly

Karel Miarka17:01:54

Hello, is there a way and example how to define Malli schema for functions with optional named arguments like this:

(defn my-named-args-fn [& {:keys [x y z]}]
  (println x y z))

(my-named-args-fn :x 1 :y 2)

ikitommi17:01:42

no simple built-in for that, but:

(defn my-named-args-fn [& {:keys [x y z]}]
  (println x y z))

(malli.destructure/infer #'my-named-args-fn)
;[:=>
; [:cat
;  [:altn
;   [:map [:map
;          [:x {:optional true} :any]
;          [:y {:optional true} :any]
;          [:z {:optional true} :any]]]
;   [:args [:* [:alt 
;               [:cat [:= :x] :any]
;               [:cat [:= :y] :any]
;               [:cat [:= :z] :any] [:cat :any :any]]]]]]
; :any]

Noah Bogart18:01:42

should that be an :or? maybe I don't remember how that destructuring works

escherize23:01:24

to add, if you use mx/defn on that shape, the destructuring schema will be inferred. ( I think ).

escherize23:01:33

(require '[malli.experimental :as mx])

(mx/defn my-named-args-fn [& {:keys [x y z]}]
  (println x y z))

(:schema (meta #'my-named-args-fn))

;; => [:=>
;; =>  [:cat
;; =>   [:altn
;; =>    [:map
;; =>     [:map
;; =>      [:x {:optional true} :any]
;; =>      [:y {:optional true} :any]
;; =>      [:z {:optional true} :any]]]
;; =>    [:args
;; =>     [:* [:alt
;; =>          [:cat [:= :x] :any]
;; =>          [:cat [:= :y] :any]
;; =>          [:cat [:= :z] :any] [:cat :any :any]]]]]]
;; =>  :any]

escherize23:01:32

you could have a function that return that shape ^

ikitommi06:01:17

@UEENNMX0T :alt & :altn are the or for sequences. That should work ok.

ikitommi06:01:18

btw, like with mostly everything else in malli, you can configure the dest inferring with options, “closed map with required keys”:

(md/infer #'my-named-args-fn {::md/closed-maps true, ::md/required-keys true})
;[:=>
; [:cat
;  [:altn
;   [:map [:map {:closed true}
;          [:x :any]
;          [:y :any]
;          [:z :any]]]
;   [:args [:* [:alt
;               [:cat [:= :x] :any]
;               [:cat [:= :y] :any]
;               [:cat [:= :z] :any]]]]]]
; :any]

ikitommi06:01:46

it would be easy to build a custom schema to cover the both cases (map or varargs), e.g.:

[:=>
 [:cat
  [:kvmap
   [:x :any]
   [:y :any]
   [:z :any]]]
 :any]
… that would work the same. haven’t needed so it’s not there yet.

Karel Miarka08:01:38

Thanks a lot. I will play with that and would like to create a fn to simplify the declaration.

Michael Gardner20:01:33

I have some namespaces that use :malli/schema function metadata, with (m.inst/collect!) as the final line in each namespace. That stopped working in 0.9.0:

Syntax error (ClassNotFoundException) compiling at (me/foo.clj:180:1).       
me.foo
Was this ever intended to work?

Michael Gardner18:01:35

@U055NJ5CC I don't see anything about this in the 0.9 changelog. Should I whip up a full repro? I'd thought there were others doing this, but maybe I'm the only one?

ikitommi11:01:03

there should not be any breakage. Please create an issue with minimal repro.

ikitommi11:01:13

works here ok :thinking_face:

(ns demo)

(defn plus
  "adds two numbers together"
  {:malli/schema [:=> [:cat :int :int] :int]}
  [z y]
  (+ z y))

(require '[malli.instrument])
(malli.instrument/collect! {:ns 'demo})
;#{{:clj {demo {plus {:schema [:=> [:cat :int :int] :int]
;                     :ns demo, :name plus}}}}}

Bart Kleijngeld09:02:20

For what it's worth: I'm having the same problem. Did you find out what was the matter @U01R1SXCAUX?

Bart Kleijngeld10:02:47

To be clear: • (mi/collect!) does not work • (mi/collect! {:ns 'demo}) does Upon closer inspection, the 0-arity call defaults to using *ns* as namespace. In my case its value was different from what I expected during my REPL session. Maybe the same for you Michael.

ikitommi11:02:20

Ugh, that doesn't look right. Not using the 0-arity, it's clearly broken.

ikitommi11:02:33

Issue welcome, should be easy to fix

👍 2
ikitommi12:02:10

fixed in master.

🙌 2
Varghiz clj21:01:26

Hi, I'm looking for component/solution where I can transform malli schema to clerk to document the schema/spec in html format... This is just a start but ultimate need is to convert it to something I can publish in confluent wiki

👍 2
ikitommi06:01:33

rendering schemas + also schema editors would be awesome.

2
escherize23:01:06

dumb question: why not use this as a malli-schema malli schema? [:fn m/schema] I guess you can’t use it to generate, but it’ll work for certain things.

ikitommi06:01:14

yes, that works if you rely on global options.