Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mx/defn and optional named arguments? #1003

Open
iterati opened this issue Jan 30, 2024 · 4 comments
Open

mx/defn and optional named arguments? #1003

iterati opened this issue Jan 30, 2024 · 4 comments

Comments

@iterati
Copy link

iterati commented Jan 30, 2024

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.

@larkery
Copy link

larkery commented Jan 31, 2024

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.

@larkery
Copy link

larkery commented Jan 31, 2024

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.

@iterati
Copy link
Author

iterati commented Jan 31, 2024

Thank you @larkery for the speedy answer. I'll move away from the named arguments style and pass in an opt map.

@ikitommi
Copy link
Member

ikitommi commented Feb 4, 2024

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]]]}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants