malli

https://github.com/metosin/malli :malli:
elarouss 2020-12-23T07:12:01.238900Z

I was thinking about something like

[:vector
     [:map [:type :work] [:value string?]]
     [:map [:type :personal] [:value string?]]]
but it didn’t work Thank you @alpox i’ll probably go with your first suggestion

ikitommi 2020-12-23T08:09:10.239400Z

both emit results like:

([{:type :personal, :value ""} {:type :work, :value ""}]
 [{:type :work, :value "l"} {:type :personal, :value ""}]
 [{:type :personal, :value "z1"} {:type :personal, :value ""}]
 [{:type :personal, :value "ZG"} {:type :personal, :value "N"}]
 [{:type :personal, :value "qQ6"} {:type :work, :value "A8h"}]
 [{:type :personal, :value "2588"} {:type :personal, :value "Djv12"}]
 [{:type :work, :value ""} {:type :personal, :value "c"}]
 [{:type :work, :value "U2uHMI"} {:type :personal, :value "376ihr"}]
 [{:type :personal, :value "n6Ct4d"} {:type :personal, :value "V09xL8"}]
 [{:type :work, :value "9"} {:type :personal, :value ""}])

ikitommi 2020-12-23T08:10:55.239600Z

if the first map is always work and second personal, :tuple for the win:

[:tuple
 [:map [:type :work] [:value string?]]
 [:map [:type :personal] [:value string?]]]

elarouss 2020-12-23T08:22:52.239800Z

Thank you, the :multi works for my use case 🙏

borkdude 2020-12-23T08:42:55.240900Z

@ikitommi In other languages people usually define this as an ADT. Maybe :enum can also be used for this?

borkdude 2020-12-23T08:43:16.241300Z

(I didn't see any docs about :enum in the README, only examples)

ikitommi 2020-12-23T08:43:59.241800Z

:enum is about values, :or is about schemas:

[:or
 [:map [:type [:= :work]] [:value string?]]
 [:map [:type [:= :personal]] [:value string?]]]
fixed, thanks to @borkdude

borkdude 2020-12-23T08:44:21.242400Z

right, that's what I was thinking

ikitommi 2020-12-23T08:44:33.242600Z

but, :or does a linear scan over schema, :multi can be much more efficient.

borkdude 2020-12-23T08:45:41.243900Z

why did you have to write [:work [:map [:type [:= :work]] [:value string?]]] with :multi but [:map [:type :work] [:value string?]] with :or?

ikitommi 2020-12-23T08:45:42.244Z

(`either` was deprecated in Plumatic Schema because of this, malli supports that as it’s usefull to describe the real world, despite being slow)

ikitommi 2020-12-23T08:46:14.244700Z

could be:

[:work [:map [:type string?] [:value string?]]]

borkdude 2020-12-23T08:46:27.245200Z

if you support :case for a closed world of possibilities it could possibly be even faster?

ikitommi 2020-12-23T08:46:34.245400Z

… but the generator would then just generate any keyword for the :a:

ikitommi 2020-12-23T08:47:05.246200Z

what kind of code would the :case emit?

borkdude 2020-12-23T08:47:08.246300Z

I was referring to [:type [:= work]] vs [:type :work]

ikitommi 2020-12-23T08:47:44.247Z

oh, that’s my bug. [:type :work] doesn’t work.

ikitommi 2020-12-23T08:47:59.247400Z

looks for :work from the registry, which doesn’t exist.

borkdude 2020-12-23T08:48:09.247700Z

ah ok then

borkdude 2020-12-23T08:48:32.248400Z

(I have no idea what :case would emit, just bouncing an idea)

ikitommi 2020-12-23T08:48:33.248500Z

[:= :work] is basically same as [:enum :work]

borkdude 2020-12-23T08:48:58.249200Z

so [:map [:enum :work :personal] [:value string?]] would also work

ikitommi 2020-12-23T08:49:38.250Z

[:map [:type [:enum :work :personal]] [:value string?]] yes

ikitommi 2020-12-23T08:50:07.250600Z

but if the :type effects the :value , or any other parts of the schema, then :multi is the way.

borkdude 2020-12-23T08:51:18.251600Z

does it generate a multi-method behind the scenes?

ikitommi 2020-12-23T08:51:35.251900Z

no, it’s closed and immutable by design.

ikitommi 2020-12-23T08:52:02.252500Z

just a dispatch map from key -> schema. and the key is looked up using the :dispatch function.

ikitommi 2020-12-23T08:52:28.253100Z

:dispatch function is applied to value, which returns the :multi key, which selects the schema

ikitommi 2020-12-23T08:53:09.253800Z

and, :dispatch can be a sci-function, of course 🙂

ikitommi 2020-12-23T08:53:25.254100Z

(m/validate
  [: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

ikitommi 2020-12-23T08:54:12.254700Z

so, there is always a map-lookup, so not as fast as case, but it would require code-generation, which would mean a macro.

borkdude 2020-12-23T08:55:15.255100Z

yeah, that makes sense

ikitommi 2020-12-23T08:57:30.257200Z

could add a :conditional, where the entry keys are functions, and the first one will match. Like :or, but more explicit and short-circuits on first match:

[:conditional
 [map? [:map [:x int?]]]
 [int? [:int {:min 1, :max 2}]]]

ikitommi 2020-12-23T08:58:28.258Z

malli.core is distilled into tons of helper functions to build new schemas, really easy to add things like this atm.

borkdude 2020-12-23T08:59:48.258200Z

doesn't :or short-circuit?

borkdude 2020-12-23T09:01:51.259500Z

btw, I also implemented case as a map-lookup in sci, same problem ;)

1👍
ikitommi 2020-12-23T09:04:05.260100Z

sorry, it does. I think the issue is that you can have multiple branches that would match, making the rest effectively unreachable.

ikitommi 2020-12-23T09:06:07.261400Z

also, the :or tranformation is bit hacky, depending whether you are doing decoding or encoding, we need to check if the transformation turned the value valid or not. that’s slow.

ikitommi 2020-12-23T09:06:24.261700Z

… but, works 🙂