wrote an issue for discussing about auto-updating with container schemas: https://github.com/metosin/malli/issues/304. Comments most welcome.
function schemas, comments welcome: https://github.com/metosin/malli/issues/125#issuecomment-727555388
I think you are ignoring the fact that different arities can have different return types/schemas
I think clojure.spec is also making this too difficult. In clj-kondo I chose to define the spec per arity
It is more verbose, but at least easy to verify
good point, the schema also forces the return to be the same for all arities.
really?
I guess there are lot of examples in the core where the different arities return different things?
yes.
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.
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
look at the spec that will result from this: https://github.com/borkdude/speculative/blob/master/src/speculative/core.cljc#L297-L302
I think this is not ergonomic at all. spec has to do backtracking etc, to match the right arity.
imo the fn spec should match the structure of the defn args+bodies
good point. what would be a good malli definition for this:
(defn fun
([x] x)
([x y] [x (* x x)]))
clj-kondo has a good syntax for the different arities, use thatโish?
something like:
(m/=> fun {:arities {1 {:output int?
:input [:tuple int?]}
2 {:output [:tuple int? pos-int?]
:input [:tuple int? int?]}}})
That's how I do it in clj-kondo yes.
Not sure if that's the best, but I optimized for matching speed, so clj-kondo can find the right arg types fast
as for defn syntax, maybe:
(defn fun
([x :- int?] :- int?
x)
([x :- int? y :- string?] :- [:tuple string? int?]
[y (* x x)]))
so the return type directly after the arg vec
๐
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?]]])
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?]}}})
could work yes
doesn't fn spec hinge on sequence specs which are not exposed yet?
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.
first demo of the function schemas will be with non-vargargs. enough to get feedback & start with the clj-kondo integration.
(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
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?]]"}