malli
malli copied to clipboard
mx/defn and optional named arguments?
I'm attempting to spec a function where I destructure named arguments with defaults. I'm not sure where I'm going wrong here:
(mx/defn init! :- :map
[& {:keys [level :- :keyword
appenders :- :keyword
output-fn :- :function]
:or {level :info
appenders core/default-appenders
output-fn output-json-fn}}]
(core/init! level appenders output-fn))
(mi/instrument!)
(init! 1) ;; => clojure.lang.ExceptionInfo as expected
(init! :level 1) ;; => {:min-level 1 ..}, didn't validate the input
(md/infer #'init!) ;; => :any for all types?
;; [:=>
;; [:cat
;; [:altn
;; [:map
;; [:map
;; [:level {:optional true} :any]
;; [:- {:optional true} :any]
;; [:keyword? {:optional true} :any]
;; [:appenders {:optional true} :any]
;; [:map? {:optional true} :any]
;; [:output-fn {:optional true} :any]
;; [:fn? {:optional true} :any]]]
;; [:args
;; [:*
;; [:alt
;; [:cat [:= :level] :any]
;; [:cat [:= :-] :any]
;; [:cat [:= :keyword?] :any]
;; [:cat [:= :appenders] :any]
;; [:cat [:= :map?] :any]
;; [:cat [:= :output-fn] :any]
;; [:cat [:= :fn?] :any]
;; [:cat :any :any]]]]]]
;; :any]
I'm new to malli, so it might be something obvious I'm missing.
I have just literally tried the same thing; md/parse
does not handle this case as far as I can tell.
(require '[malli.destructure :as md])
(md/parse '[& {:keys [a :- :int]}])
;; => {:raw-arglist [& {:keys [a :- :int]}],
;; :parsed {:elems [], :rest {:amp &, :arg {:arg [:map {:keys [a :- :int]}]}}},
;; :arglist [& {:keys [a :- :int]}],
;; :schema
;; [:cat
;; [:altn
;; [:map
;; [:map
;; [:a {:optional true} :any]
;; [:- {:optional true} :any]
;; [:int {:optional true} :any]]]
;; [:args
;; [:*
;; [:alt
;; [:cat [:= :a] :any]
;; [:cat [:= :-] :any]
;; [:cat [:= :int] :any]
;; [:cat :any :any]]]]]]}
(md/parse '[& {:keys [a]} :- [:map [:a :int]]])
;; => {:raw-arglist [& {:keys [a]} :- [:map [:a :int]]],
;; :parsed
;; {:elems [],
;; :rest {:amp &, :arg {:arg [:map {:keys [a]}], :- :-, :schema [:map [:a :int]]}}},
;; :arglist [& {:keys [a]}],
;; :schema [:cat [:map [:a :int]]]}
The second case is roughly what we want the first case to do I believe.
Actually the second case also doesn't work, because the instrumented function is expected to have arity 1 rather than arity 2. So unless there is another syntax to use I don't think mx/defn
can do this right now.
Thank you @larkery for the speedy answer. I'll move away from the named arguments style and pass in an opt map.
There is no support for defining types for destructured keys atm. The Schematize Syntax is taken from Plumatic Schema as Cursive understands that too.
Support for this would be simple to add but would add ambiquity issue, e.g. the following (valid clojure!) would not work:
(let [{:keys [a :- :int]} {:a 1, :- 2, :int 3}]
[a - int])
; => [1 2 3]
So, this does not work:
(md/parse ['{:keys [a :- :int]}])
;{:raw-arglist [{:keys [a :- :int]}],
; :parsed {:elems [{:arg [:map {:keys [a :- :int]}]}], :rest nil},
; :arglist [{:keys [a :- :int]}],
; :schema [:cat
; [:altn
; [:map [:map [:a {:optional true} :any] [:- {:optional true} :any] [:int {:optional true} :any]]]
; [:args [:schema [:* [:alt [:cat [:= :a] :any] [:cat [:= :-] :any] [:cat [:= :int] :any] [:cat :any :any]]]]]]]}
But this does:
(md/parse ['{:keys [a]} :- [:map [:a :int]]])
;{:raw-arglist [{:keys [a]} :- [:map [:a :int]]],
; :parsed {:elems [{:arg [:map {:keys [a]}], :- :-, :schema [:map [:a :int]]}], :rest nil},
; :arglist [{:keys [a]}],
; :schema [:cat [:map [:a :int]]]}
IMO Clojure should add support for optional type definitions, via plain :
like TC39 is for JavaScript. With that:
(md/parse ['{:keys [a : :int]}])
;{:raw-arglist [{:keys [a : :int]}],
; :parsed {:elems [{:arg [:map {:keys [a]}], EMPTYKW EMPTYKW, :schema [:map [:a :int]]}], :rest nil},
; :arglist [{:keys [a : :int]}],
; :schema [:cat [:map [:a :int]]]}