Fork me on GitHub
Markus Str11:11:51

Hi, I'm getting started with malli and this migh be a noob question, but why is this an invalid schema error? (I took the source of string? in cljs and changed it to str2? Weirdly string? is valid and the alias isn't

(defn ^boolean str2?
  "Returns true if x is a JavaScript string."
  (goog/isString x))
(def Test
   [:url str2?]
   [:det map?]

(-> Test
    (m/explain {:url "sa" :det {:a 5}})

Markus Str11:11:44

Why can't I define a boolean predicate function there?


@US22FPMPX Any reason you're not using string?

Lucy Wang11:11:01

@US22FPMPX try [:fn str2]

👍 3

FWIW string? on CLJS is:

(defn ^boolean string?
  "Returns true if x is a JavaScript string."
  (goog/isString x))

Markus Str11:11:54

thanks for the quick repsonse @U04V15CAJ, str2? was just for figuring out if my function was the issue

Markus Str11:11:13

@UP90Q48J3 thanks that works actually, but error messages are not defined then

Markus Str11:11:19


(defn twitter-url? [s] (re-find #"http[s]?://twitter.*\d+" s))
(def Test
   [:url [:fn twitter-url?]]
   [:det map?]
(-> Test
    (m/explain {:url "" :det {:a 5}})

Markus Str11:11:26

(-> Test
    (m/explain {:url "" :det {:a 5}})

Markus Str11:11:32

{:url ["unknown error"]} then


Maybe malli expects the function to return a boolean instead of nil? don't know

Markus Str11:11:20

Thanks for the pointer


nils should be ok, also regexs:

 [:x #"http[s]?://twitter.*\d+"]
 [:y [:fn {:error/message "invalid"} (constantly false)]]

Markus Str12:11:07

Ah interesting, so regex work, but I can't have something like:

[:x #(re-find #"http[s]?://twitter.*\d+" %)]
Probably have to add to the default schema registry then, to have it work naturally like, [:x twitter-url?] I guess?

Markus Str12:11:02

I'm going to read the docs for the third time; seems my mental model is not there yet!

Markus Str12:11:00

(defn twitter-url? [s] (re-find #"http[s]?://twitter.*\d+" s))
(def -twitter-url? [:fn {:error/message "invalid twitter url"} twitter-url?])

(def Test
   [:url -twitter-url?]
   [:det map?]
(-> Test
    (m/explain {:url "" :det {:a 5}})
So this is how I could do it; only backdraw is that I have two functions now; one for normal use, the other specifically for validation


could make the predicate schemas implement IFn, so you could:

(def twitter-url?
    [:re {:error/message "invalid twitter url"}

(twitter-url? "")
; => true

(m/validat twitter-url? "")
; => true

Markus Str12:11:01

Looks promising. For me personally (and I might be totally off) it could be nice for malli to also accept predicate functions directly inside the spec-vector

(defn twitter-url? [s] (re-find #"http[s]?://twitter.*\d+" s))
(def Test
   [:url twitter-url?]
For now I'll read up on the registry and how to add it there


What's the reason it requires [:fn ..] right now @U055NJ5CC?


could add an option to allow that shortcut (as there is regexs), but in short: it’s too easy to write schemas which do not serialize.


but if one is not looking for serialization, it would be easier for sure.


good point. maybe for some people serializing a schema is not important?


need to revisit these for 1.0.0. but will add optional support for plain functions.

🙌 3

also, the default registry being immutable. it’s currently easy to use immutable registries, but hard to get a global mutable registry. both could be easy.


Do you think malli will be able to do something like grasp?


as in, support the things that spec does to match s-expressions


I believe so, as soon as there are the sequence schemas.

Lucy Wang12:11:32

> good point. maybe for some people serializing a schema is not important? to be frank I think quite some (if not most) people don't need that ...


is there something off with generators and the [:and] clause ?

(def schema [:and map?
              [:foo string?]]])

(mg/generate schema)
throws an error ("Couldn't satisfy such-that predicate after 100 tries.") this, however, works:
(def schema [:and
              [:foo string?]]

(mg/generate schema)
my hunch is that it's unable to "deduce" that it should first try to generate the :map , and starts by generating a completely random map which of course never qualifies the [:map [:foo string?]] schema?


yes, the order matters


still seems like there's something off with :and :thinking_face:

(def base [:map
           [:foo [:enum :a :b]]])
(def x [:and base
        [:multi {:dispatch :foo}
         [:a [:map [:i string?]]]
         [:b [:map [:j pos-int?]]]]])
seems like mg/generate is unable to generate values for this schema as well



(defmethod -schema-generator :and [schema options] (gen/such-that (m/validator schema options) (-> schema (m/children options) first (generator options)) 100))


^:-- the first one is used for the generator.


but that means there isn't really a way for me to work around it except writing a custom generator, right ?


(which is what i just did)


you could merge the base schemas, but merge might not work with :multi. but, there are options, like:

   [:multi {:dispatch :foo}
    [:a [:merge ::base [:map [:i string?]]]]
    [:b [:merge ::base [:map [:j pos-int?]]]]]]
  {:registry (merge
               {::base [:map [:foo [:enum :a :b]]]})})


merge doesn’t work with :multi currently, this would be best way to do it (if it worked):

  [:map [:foo [:enum :a :b]]]
  [:multi {:dispatch :foo}
   [:a [:map [:i string?]]]
   [:b [:map [:j pos-int?]]]])
;[:multi {:dispatch :foo}
; [:a [:map [:i string?]]]
; [:b [:map [:j pos-int?]]]]


i just wrote a helper function which just does this:

(defn merged
  "Given a list of generators, returns a generator that applies `merge`
  to all the generator results."
  [& gens]
  (gen/fmap (fn [args]
              (reduce merge {} args))
            (apply gen/tuple gens)))

(def schema [:and {:gen/gen (merged (mg/geneator a) (mg/generator b)} a b])

👍 3

:multi should do type-inferring, so that all map-like :multis act like a map.


that's what i was going to say


:multi is pretty much always a map anyway


unless it’s a tuple / sequence:

  [:multi {:dispatch 'first}
   [:sized [:tuple keyword? [:map [:size int?]]]]
   [:human [:tuple keyword? [:map [:name string?] [:address [:map [:country keyword?]]]]]]]
  [:human {:name "seppo", :address {:country :sweden}}])
; true


but, it’s easy to figure out if it looks like a map and could be merged.

Maciej Falski19:11:10

Hi, I’ve just started with malli, and I’m spiking converting our spec definitions to malli-based. Among others, we’ve defined specs with generators and json decoding for java-time (aka java8) types , like instant, duration , and interval. It’s easy with spec-tools. Now I’m trying to do the same with malli and this is what I came up with:

(def Instant
    {:pred            t/instant?
     :type-properties {:error/message "should be an instant"
                       :decode/json   t/instant
                       :encode/json   str
                       :gen/gen       (gen/fmap (fn [[d h m s]] (t/plus (t/instant) (t/days d) (t/hours h) (t/minutes m) (t/seconds s)))
                                                (gen/tuple gen/int gen/int gen/int gen/int))}}))
Does it makes sense? Would there be a better way? I’m aware of this, but it’s still open.