Fork me on GitHub
#malli
<
2020-11-15
>
ikitommi09:11:52

wrote an issue for discussing about auto-updating with container schemas: https://github.com/metosin/malli/issues/304. Comments most welcome.

borkdude11:11:34

I think you are ignoring the fact that different arities can have different return types/schemas

borkdude11:11:04

I think clojure.spec is also making this too difficult. In clj-kondo I chose to define the spec per arity

borkdude11:11:17

It is more verbose, but at least easy to verify

ikitommi11:11:23

good point, the schema also forces the return to be the same for all arities.

ikitommi11:11:04

I guess there are lot of examples in the core where the different arities return different things?

borkdude11:11:13

confirmed: > - The output schema always goes on the fn name, not the arg vector. This means that all arities must share the same output schema.

borkdude11:11:02

well, clojure.core/map, filter, etc, is an example that have different return types for different arities. I kinda wish that the transducer arity was just a different version like clojure.core/mapping but that ship has sailed. I often make mistakes with this

borkdude11:11:22

I think this is not ergonomic at all. spec has to do backtracking etc, to match the right arity.

borkdude11:11:48

imo the fn spec should match the structure of the defn args+bodies

ikitommi11:11:44

good point. what would be a good malli definition for this:

(defn fun
  ([x] x)
  ([x y] [x (* x x)]))

ikitommi11:11:14

clj-kondo has a good syntax for the different arities, use that’ish?

ikitommi11:11:53

something like:

(m/=> fun {:arities {1 {:output int?
                        :input [:tuple int?]}
                     2 {:output [:tuple int? pos-int?]
                        :input [:tuple int? int?]}}})

borkdude11:11:30

That's how I do it in clj-kondo yes.

borkdude11:11:11

Not sure if that's the best, but I optimized for matching speed, so clj-kondo can find the right arg types fast

borkdude11:11:22

as for defn syntax, maybe:

(defn fun
  ([x :- int?] :- int?
   x)
  ([x :- int? y :- string?] :- [:tuple string? int?]
   [y (* x x)]))

borkdude11:11:40

so the return type directly after the arg vec

ikitommi11:11:58

the extracted schemas could be in the compact/short :=> format, which is always 1-arity:

(defn fun1 [x] (* x x))

;; short
(m/=> fun1 [:=> int? [:tuple pos-int?]])
(defn fun
  ([x] (fun x x))
  ([x y] [x (* x x)]))

;; short
(m/=> fun [:or
           [:=> int? [:tuple int?]]
           [:=> [:tuple int? pos-int?] [:tuple int?]]])

ikitommi11:11:00

the long versions:

(defn fun1 [x] (* x x))

;; long
(m/=> fun1 {:arities {1 {:input int?
                         :output [:tuple pos-int?]}}})
(defn fun
  ([x] (fun x x))
  ([x y] [x (* x x)]))

;; long
(m/=> fun {:arities {1 {:output int?
                        :input [:tuple int?]}
                     2 {:output [:tuple int? pos-int?]
                        :input [:tuple int? int?]}}})

borkdude11:11:00

could work yes

borkdude11:11:48

doesn't fn spec hinge on sequence specs which are not exposed yet?

ikitommi12:11:14

yes, those are needed for varags. we just had an internal tech-talk on friday, did a plan how to get the sequence schemas & schema parsing out. takes few days to make that good.

ikitommi12:11:57

first demo of the function schemas will be with non-vargargs. enough to get feedback & start with the clj-kondo integration.

👍 3
ikitommi16:11:37

(require '[malli.schema :as ms])

(ms/defn ^:always-validate fun :- [:tuple int? pos-int?]
  "returns a tuple of a number and it's value squared"
  ([x :- int?] :- any? ;; arity-level override
   (fun x x))
  ([x :- int?, y :- int?] ;; uses the default return
   [x (* x x)]))

(clojure.repl/doc fun)
; -------------------------
; demo/fun
; ([x] [x y])
;   
;   [:-> [:tuple int?] any?]
;   [:-> [:tuple int? int?] [:tuple int? pos-int?]]
; 
;   returns a tuple of a number and it's value squared

ikitommi16:11:52

full meta:

(ms/defn square :- pos-int?
  [x :- int?]
  (* x x))

(meta #'square)
;{:schema [:or [:-> [:tuple int?] pos-int?]],
; :ns #object[clojure.lang.Namespace 0x3c5f3ba8 "demo"],
; :name square,
; :file "/Users/tommi/projects/metosin/malli/src/malli/schema.cljc",
; :column 1,
; :raw-arglists ([x :- int?]),
; :line 64,
; :arglists ([x]),
; :doc "\n[:or\n [:-> [:tuple int?] pos-int?]]"}