Fork me on GitHub
#malli
<
2021-12-29
>
ikitommi09:12:01

question: a new schema for "map or a sequence of map entries" or just an new property to :map schema? Relates to https://clojure.org/news/2021/03/18/apis-serving-people-and-programs.

ikitommi09:12:43

a) (m/validate [:map-like [:a :int]] [[:a 1]]) ;=> true b) (m/validate [:map {:coerce true}: [:a :int]] [[:a 1]]) ;=> true

ikitommi09:12:55

I'm currently thinking of going with a, as this is a special case. Both ways, it's just few lines of extra code I think

ikitommi09:12:43

also, what would be a good name for a new schema type, :map-like ? :every ?

ikitommi09:12:47

do you @nbtheduke the opinion for this?

Ben Sless09:12:51

:entries

👍 1
Ben Sless10:12:48

Then :map is :entries + map?

ikitommi10:12:03

spec seems to have s/keys* for this

ikitommi10:12:17

thing is, transformers, parsers and explainers need separate code if we want to retain the original entry sequence. forcing the data to be a map allows us to reuse the current code.

ikitommi10:12:38

for the Keyword argument functions now also accept maps thing, we just need a map, so a simple new way to coerce the entry sequence into a map would do.

Noah Bogart13:12:57

One thing to note, the input isn't a sequence of map entries, but a sequence of alternating keys and values, which can be in any order

ikitommi13:12:58

yes. I guess this is correct:

(require '[malli.destructure :as md])
(require '[malli.generator :as mg])

(defmethod mg/-schema-generator :any [_ _] (mg/generator :string))

(-> '[& {:as m :keys [id before after]}]
    (md/parse)
    :schema
    (mg/sample {:size 10, :seed 42}))
;(({:after ""})
; (:id "F")
; ()
; ({:after ""})
; (:before "n" :after "ai2" :id "5FI0" :id "")
; ({:after "qP3t"})
; ()
; ({:id "j"})
; ()
; (:before
;  "2"
;  :after
;  "HW5Mn3"
;  :after
;  "gv9m93"
;  :before
;  "GVDI2b"
;  :before
;  "fhR"
;  :before
;  "F8562"
;  :after
;  "lS"
;  :before
;  "Z7y0nz"
;  :before
;  "7G"))

Noah Bogart15:12:26

this matches the implementation, as far as i can tell! very cool

Noah Bogart15:12:57

looks like you’re missing this from the test suite, i’m not sure if you’re missing it from the schema:

user=> (defn example [{:keys [id before after] :as m}] [id before after m])
#'user/example
user=> (example '(:id 1 :before 2 :after 3 :missing 4))
[1 2 3 {:id 1, :before 2, :after 3, :missing 4}]

Noah Bogart15:12:57

which is to say, the bind is a destructured map, but the input can be a list of key-value pairs

Noah Bogart15:12:12

weird thing, it can’t be a vector:

user=> (example [:id 1 :before 2 :after 3 :missing 4])
[nil nil nil [:id 1 :before 2 :after 3 :missing 4]]

ikitommi15:12:50

you can pass in it as a list 🤯 by default???

Noah Bogart15:12:53

hah yeah, it’s not really talked about anywhere, but the current version of clojure.core/destructure allows it: https://github.com/clojure/clojure/blob/clojure-1.11.0-alpha3/src/clj/clojure/core.clj#L4434-L4439

ikitommi18:12:18

@nbtheduke, added support for it too. starts to smell like a new :map-destructuring Schema, which would hide the sequential part. Ugly, but works(?):

(-> '[a {:keys [b c]
         :strs [d e]
         :syms [f g]
         :or {b 0, d 0, f 0} :as map}]
    (md/parse)
    :schema)
;[:cat
; :any
; [:altn
;  [:map [:map
;         [:b {:optional true} :any]
;         [:c {:optional true} :any]
;         ["d" {:optional true} :any]
;         ["e" {:optional true} :any]
;         ['f {:optional true} :any]
;         ['g {:optional true} :any]]]
;  [:args [:schema
;          [:*
;           [:alt
;            [:cat [:= :b] :any]
;            [:cat [:= :c] :any]
;            [:cat [:= "d"] :any]
;            [:cat [:= "e"] :any]
;            [:cat [:= 'f] :any]
;            [:cat [:= 'g] :any]
;            [:cat :any :any]]]]]]]

Noah Bogart18:12:56

amazing. thanks so much for tackling this. destructuring in clojure is really weird haha

ikitommi18:12:22

(def Schema
  (-> '[a {:keys [b c]
           :strs [d e]
           :syms [f g]
           :or {b 0, d 0, f 0} :as map}]
      (md/parse)
      :schema))

(m/parse Schema [1 {:b 1, 'f 3, "e" 2, :extra 42}])
; => [1 [:map {:b 1, f 3, "e" 2, :extra 42}]]

(m/parse Schema [1 '(:c 1, , f 3, "e" 2, :extra 42)])
; => [1 [:args [[:c 1] [f 3] ["e" 2] [:extra 42]]]]

ikitommi14:12:42

a thought experiment, should we have more argument relationship markers, e.g. :- (is a) and :< (a subset of)? could also go EXTREME EVIL and introduce real math symbols like :⊂ 👿

(def User 
  [:map 
   [:id :uuid] 
   [:name :string] 
   [:age :int]])

;; argument is exactly User
[{:keys [id age]} :- User]
; => [:cat [:map [:id :uuid] [:name :string] [:age :int]]]

;; argument should be subset of user (mark others as optional)
[{:keys [id age]} :< User]
; => [:cat [:map [:id :uuid] [:name {:optional true} :string] [:age :int]]]

🙈 1
ikitommi14:12:48

also, would be awesome if tools like #cursive and #calva would have special markers for the type hints, e.g. dim them out so it’s easier to read.

ikitommi14:12:07

… for fully qualified keys, the key definitions could be pulled from the registry:

(mm/def ::id :uuid)
(mm/def ::name :string)
(mm/def ::age :int)

;; argument is exactly User
[{::keys [id age]}]
; => [:cat [:map ::id ::age]]

dharrigan14:12:36

We use (still 😞 ) compojure-sweet for some of our APIs, and I see a lot of :- in the path-params, body.... etc...and well, personally, I found it hard to know what :- meant etc...

ikitommi14:12:25

in compojure-api, there is a lot of extra syntax, also the fnk syntax. sorry for all that 🙂

dharrigan14:12:49

I have to deal with it every day 😞 I definitely see lessons where learnt with the new improved reitit library 🙂

dharrigan14:12:23

So, I'm not for, nor against, additional markers, I'm rather on the fence (just more stuff to learn I suppose!)

ikitommi14:12:11

yeah, any support for inline typehints is a compromise. If IDEs would support that properly and there would be just one way to doing those, would be great.

Noah Bogart15:12:16

i think i’d prefer words instead of single characters for such things. :exact or :exactly or :is-a are easier to parse than :- in my opinion. subset feels like Typescript-style structural typing, which clojure supports out of the box with open maps

dharrigan15:12:07

I'm with Noah on this one too, I would have to translate :- into is-a in my head as well. Nothing wrong in being wordy, when it comes to comprehension.

dharrigan15:12:49

my 2c`s 🪙

ikitommi15:12:37

^:-- that’s what I would love to have, to remove the type/schema clutter if used.

Noah Bogart15:12:07

ah, i understand now that :- is supposed to be like static type declarations, that makes more sense

👍 2
Yehonathan Sharvit16:12:32

Is there a commonly agreed way to document the meaning of each value in a :enum?

ikitommi18:12:33

@viebel don’t think there is. ideas welcome

Ben Sless18:12:07

enumn Where the keys are the enumeration and values are properties

Yehonathan Sharvit07:12:23

I don't get what you mean @UK0810AQ2

Ben Sless07:12:46

Like the map syntax, where the keys are the enumeration and optionally you could provide a properties map

ikitommi08:12:11

:enumn (or similar) would be good for key->value pair mappings.

ikitommi08:12:00

(def MyEnum
  [:enumn
   [:small {:description "so small"} "small"]
   [:medium {:description "such medium"} "medium"]])

ikitommi08:12:28

(m/validate MyEnum "small") ; => true

ikitommi08:12:00

(m/parse MyEnum "small") ; => [:small "small"]

ikitommi08:12:04

:thinking_face:

ikitommi08:12:26

e.g. :cat + :catn, :or + : orn, …

Yehonathan Sharvit08:01:28

I like the idea of enumn . I am wondering if there is any meaning to the keys (e.g. :small and :medium ).

Ben Sless08:01:48

I was thinking of a slightly different semantic, [:enumn [v1 {:doc "foo}] [v2] v3], where the keys are the enumerations, and the syntax can be value | [value ?properties]

Yehonathan Sharvit05:01:45

I prefer what you suggested @UK0810AQ2 as we don't have to duplicate the enum values