Fork me on GitHub
#malli
<
2022-09-18
>
Ory Band11:09:14

Hi. I'm trying ot use a default fn value transformer from the tips section (https://github.com/metosin/malli/blob/master/docs/tips.md#default-value-from-a-function) and also discussed here https://clojurians.slack.com/archives/CLDK6MFMK/p1626171933375100, but sci fails with "could not resolve symbol" with the function i'm using. Any idea why?

; (err) Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:39).
; (err) Could not resolve symbol: get-num-cpus

Ory Band11:09:15

got a response from borkdude here, unclear how to proceed though - how to pass a namespace? https://clojurians.slack.com/archives/C015LCR9MHD/p1663501053285409

ikitommi12:09:56

you can pass in the normal sci-options via :malli.core/sci-options. what ever sci accepts, is accepted here too:

(m/validate
 [:fn '(fn [arg] (malli.impl.util/-invalid? arg))]
 ::m/invalid
 {::m/sci-options {:namespaces {'malli.impl.util {'-invalid? malli.impl.util/-invalid?}}}})
; => true

ikitommi12:09:28

sci readme seems to have tips and helpers on how to bind multiple things at once.

Ory Band12:09:33

specifically in my case, how can i add my custom fn get-num-cpu?

ikitommi12:09:11

(defn get-num-cpus []
  (-> (Runtime/getRuntime) .availableProcessors))

(m/validate
 [:fn '(fn [arg] (= arg (get-num-cpus)))]
 10
 {::m/sci-options {:namespaces {'user {'get-num-cpus get-num-cpus}}}})
; => true

ikitommi12:09:17

does that solve the issue?

ikitommi12:09:30

but, do you need sci here?

Ory Band12:09:34

thanks tommi! testing, getting back to you soon

Ory Band12:09:51

hmm, i thought i did. don't i? i need to calculate the value based on amount of available cpus

ikitommi12:09:54

you could also just remove the ' and say #(hash-map :thread-count (* 5 (get-num-cpus)))

Ory Band12:09:11

wait. i could do that? i thought you couldn't pass functions

ikitommi12:09:28

m/eval takes any function or a source code. for the latter, it uses sci to interpret it.

ikitommi12:09:59

you can’t store normal functions as data (over wire / db etc), but if you don’t need that, you don’t have to use sci.

Ory Band12:09:19

and i can put a fn under the :default key and it would work?

ikitommi12:09:07

(m/validate
 [:fn #(= % (get-num-cpus))]
 10)
; => true

Ory Band12:09:31

amazing. i'm going to test that and get back to you. another question: what if i need to calculate the :default based on another key's input?

Ory Band12:09:38

not storing this as data

Ory Band12:09:05

see this example, how can i avoid sci?

[:parallel-pull-count
      {:title       "Pull messages thread count"
       :description ""
       :default 1}
      pos-int?]
     [:ack-and-lease-extension-thread-count
      {:title       "ACK processing and lease extension thread count"
       :description ""
       ; immitate java-pubsub's default value
       ; 
       :default-fn '#(max 6 (* 2 (:parallel-pull-count %)))}
      pos-int?]]]])

ikitommi12:09:31

currently, the transformation interceptors don’t see their parents, so, attaching a transformer into a map key -> just sees that key & it’s children. This could be changed, but would need an issue and some thinking on how to do that.

ikitommi12:09:09

but, you can attach the default values into the :map itself -> in that case the default-fn sees the whole map.

Ory Band12:09:49

yeah but in case the map was given by the user and one of the keys is missing, i want to support defaulting there as well

Ory Band12:09:50

btw, just tried your suggestion and it doesn't work:

:default #(hash-map :thread-count (* 5 (get-num-cpus)))}
i'm getting
; (out) clojure.lang.ExceptionInfo: Invalid configuration {"parsing_errors" {:concurrency ["invalid type"]}}

Ory Band13:09:10

what am i doing wrong?

ikitommi13:09:45

the :default key is defined in the default-value-transformer and it should be a value, e.g. (hash-map :thread-count (* 5 (get-num-cpus))). the default-fn-value-transformer defines a key :default-fn which can be a function, e.g. #(hash-map :thread-count (* 5 (get-num-cpus))).

ikitommi13:09:02

(m/decode
 [:map {:default {}}
  [:x {:default (get-num-cpus)} string?]
  [:y {:default-fn #(* (get-num-cpus) (:x %))} string?]]
 nil
 (mt/transformer
  (mt/default-value-transformer)
  (default-fn-value-transformer)))
; => {:x 10, :y 100}

Ory Band13:09:26

so i should use default-fn then

Ory Band13:09:26

it works. thank you!

🙇 1
Anders Eknert19:09:25

> currently, the transformation interceptors don’t see their parents, so, attaching a transformer into a map key -> just sees that key & it’s children. This could be changed, but would need an issue and some thinking on how to do that. Interesting. That’s just the issue I was struggling with 😅 Should I create an issue?

Anders Eknert20:09:50

To be more specific, I’m trying to get the name in the key of a :map-of construct, and use it as a default for one of the values in a sub-map